committed by
GitHub
172 changed files with 8559 additions and 11 deletions
@ -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 }), |
||||
|
}); |
||||
|
}; |
||||
@ -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 }), |
||||
|
}); |
||||
|
}; |
||||
@ -0,0 +1,89 @@ |
|||||
|
import { ExtensibleAuditedEntity, IHasConcurrencyStamp, PagedAndSortedResultRequestDto } from "../../model/baseModel"; |
||||
|
|
||||
|
export enum JobStatus { |
||||
|
None = -1, |
||||
|
Completed = 0, |
||||
|
Running = 10, |
||||
|
FailedRetry = 15, |
||||
|
Paused = 20, |
||||
|
Stopped = 30, |
||||
|
} |
||||
|
|
||||
|
export enum JobType { |
||||
|
Once, |
||||
|
Period, |
||||
|
Persistent, |
||||
|
} |
||||
|
|
||||
|
export enum JobPriority { |
||||
|
Low = 5, |
||||
|
BelowNormal = 10, |
||||
|
Normal = 0xF, |
||||
|
AboveNormal = 20, |
||||
|
High = 25 |
||||
|
} |
||||
|
|
||||
|
export interface BackgroundJobInfo extends ExtensibleAuditedEntity<string>, IHasConcurrencyStamp { |
||||
|
isEnabled: boolean; |
||||
|
name: string; |
||||
|
group: string; |
||||
|
type: string; |
||||
|
result: string; |
||||
|
args: ExtraPropertyDictionary, |
||||
|
description?: string; |
||||
|
beginTime: Date; |
||||
|
endTime?: Date; |
||||
|
lastRunTime?: Date; |
||||
|
nextRunTime?: Date; |
||||
|
jobType: JobType; |
||||
|
cron: string; |
||||
|
triggerCount: number; |
||||
|
tryCount: number; |
||||
|
maxTryCount: number; |
||||
|
maxCount: number; |
||||
|
isAbandoned: boolean; |
||||
|
interval: number; |
||||
|
priority: JobPriority; |
||||
|
lockTimeOut: number; |
||||
|
} |
||||
|
|
||||
|
interface BackgroundJobInfoCreateOrUpdate { |
||||
|
isEnabled: boolean; |
||||
|
args: ExtraPropertyDictionary; |
||||
|
description?: string; |
||||
|
beginTime: Date; |
||||
|
endTime?: Date; |
||||
|
jobType: JobType; |
||||
|
cron: string; |
||||
|
maxCount: number; |
||||
|
interval: number; |
||||
|
priority: JobPriority; |
||||
|
lockTimeOut: number; |
||||
|
} |
||||
|
|
||||
|
export interface BackgroundJobInfoCreate extends BackgroundJobInfoCreateOrUpdate { |
||||
|
name: string; |
||||
|
group: string; |
||||
|
type: string; |
||||
|
} |
||||
|
|
||||
|
export interface BackgroundJobInfoUpdate extends BackgroundJobInfoCreateOrUpdate, IHasConcurrencyStamp { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
export interface BackgroundJobInfoGetListInput extends PagedAndSortedResultRequestDto { |
||||
|
filter?: string; |
||||
|
name?: string; |
||||
|
group?: string; |
||||
|
type?: string; |
||||
|
status?: JobStatus; |
||||
|
beginTime?: Date; |
||||
|
endTime?: Date; |
||||
|
beginLastRunTime?: Date; |
||||
|
endLastRunTime?: Date; |
||||
|
beginCreationTime?: Date; |
||||
|
endCreationTime?: Date; |
||||
|
isAbandoned?: boolean; |
||||
|
jobType?: JobType; |
||||
|
priority?: JobPriority; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -0,0 +1,338 @@ |
|||||
|
<template> |
||||
|
<BasicModal |
||||
|
@register="registerModal" |
||||
|
:width="800" |
||||
|
:height="400" |
||||
|
:title="modalTitle" |
||||
|
:help-message="modalTips" |
||||
|
:mask-closable="false" |
||||
|
@ok="handleSubmit" |
||||
|
> |
||||
|
<Form |
||||
|
ref="formElRef" |
||||
|
:colon="false" |
||||
|
label-align="left" |
||||
|
layout="horizontal" |
||||
|
:model="modelRef" |
||||
|
:rules="modelRules" |
||||
|
> |
||||
|
<Tabs v-model:activeKey="activeKey"> |
||||
|
<TabPane key="basic" :tab="L('BasicInfo')"> |
||||
|
<FormItem name="isEnabled" :labelCol="{ span: 4 }" :wrapperCol="{ span: 18 }" :label="L('DisplayName:IsEnabled')"> |
||||
|
<Checkbox v-model:checked="modelRef.isEnabled">{{ L('DisplayName:IsEnabled') }}</Checkbox> |
||||
|
</FormItem> |
||||
|
<FormItem name="group" required :label="L('DisplayName:Group')"> |
||||
|
<Input :disabled="isEditModal" v-model:value="modelRef.group" autocomplete="off" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="name" required :label="L('DisplayName:Name')"> |
||||
|
<Input :disabled="isEditModal" v-model:value="modelRef.name" autocomplete="off" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="type" required :label="L('DisplayName:Type')" :extra="L('Description:Type')"> |
||||
|
<Textarea :disabled="isEditModal" v-model:value="modelRef.type" :auto-size="{ minRows: 3, maxRows: 6 }" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="beginTime" :label="L('DisplayName:BeginTime')"> |
||||
|
<DatePicker style="width: 100%;" v-model:value="modelRef.beginTime" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="endTime" :label="L('DisplayName:EndTime')"> |
||||
|
<DatePicker style="width: 100%;" v-model:value="modelRef.endTime" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="jobType" :label="L('DisplayName:JobType')" :help="L('Description:JobType')"> |
||||
|
<Select :options="jopTypeOptions" v-model:value="modelRef.jobType" :default-value="JobType.Once" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="cron" v-if="modelRef.jobType === JobType.Period" :label="L('DisplayName:Cron')" :help="L('Description:Cron')"> |
||||
|
<Input v-model:value="modelRef.cron" autocomplete="off" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="interval" v-if="modelRef.jobType !== JobType.Period" :label="L('DisplayName:Interval')" :help="L('Description:Interval')"> |
||||
|
<InputNumber style="width: 100%;" v-model:value="modelRef.interval" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="maxCount" :label="L('DisplayName:MaxCount')" :help="L('Description:MaxCount')"> |
||||
|
<InputNumber style="width: 100%;" v-model:value="modelRef.maxCount" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="maxTryCount" :label="L('DisplayName:MaxTryCount')" :help="L('Description:MaxTryCount')"> |
||||
|
<InputNumber style="width: 100%;" v-model:value="modelRef.maxTryCount" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="priority" :label="L('DisplayName:Priority')" :help="L('Description:Priority')"> |
||||
|
<Select :options="jobPriorityOptions" v-model:value="modelRef.priority" :default-value="JobPriority.Normal" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="lockTimeOut" :label="L('DisplayName:LockTimeOut')" :help="L('Description:LockTimeOut')"> |
||||
|
<InputNumber style="width: 100%;" v-model:value="modelRef.lockTimeOut" /> |
||||
|
</FormItem> |
||||
|
<FormItem name="description" :label="L('DisplayName:Description')"> |
||||
|
<Textarea v-model:value="modelRef.description" :row="3" /> |
||||
|
</FormItem> |
||||
|
<FormItem :label="L('DisplayName:Result')"> |
||||
|
<Textarea readonly v-model:value="modelRef.result" :auto-size="{ minRows: 6, maxRows: 12 }" /> |
||||
|
</FormItem> |
||||
|
</TabPane> |
||||
|
<TabPane key="paramters" :tab="L('Paramters')"> |
||||
|
<BasicTable @register="registerTable" :data-source="jobArgs"> |
||||
|
<template #toolbar> |
||||
|
<Button type="primary" @click="handleAddNewArg">{{ L('BackgroundJobs:AddNewArg') }} |
||||
|
</Button> |
||||
|
</template> |
||||
|
<template #action="{ record }"> |
||||
|
<TableAction |
||||
|
:stop-button-propagation="true" |
||||
|
:actions="[ |
||||
|
{ |
||||
|
label: L('Edit'), |
||||
|
icon: 'ant-design:edit-outlined', |
||||
|
onClick: handleEditParam.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
color: 'error', |
||||
|
label: L('Delete'), |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
onClick: handleDeleteParam.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
</TabPane> |
||||
|
</Tabs> |
||||
|
</Form> |
||||
|
<BasicModal |
||||
|
:title="L('BackgroundJobs:Paramter')" |
||||
|
@register="registerParamModal" |
||||
|
@ok="handleSaveParam" |
||||
|
> |
||||
|
<BasicForm @register="registerParamForm" /> |
||||
|
</BasicModal> |
||||
|
</BasicModal> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { computed, ref, reactive, unref, nextTick } from 'vue'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { useValidation } from '/@/hooks/abp/useValidation'; |
||||
|
import { useMessage } from '/@/hooks/web/useMessage'; |
||||
|
import { |
||||
|
Button, |
||||
|
Checkbox, |
||||
|
DatePicker, |
||||
|
Form, |
||||
|
Select, |
||||
|
Tabs, |
||||
|
Input, |
||||
|
InputNumber, |
||||
|
Textarea, |
||||
|
} from 'ant-design-vue'; |
||||
|
import { BasicForm, useForm } from '/@/components/Form'; |
||||
|
import { BasicModal, useModal, useModalInner } from '/@/components/Modal'; |
||||
|
import { BasicTable, BasicColumn, TableAction, useTable } from '/@/components/Table'; |
||||
|
import { getById, create, update } from '/@/api/task-management/backgroundJobInfo'; |
||||
|
import { JobType, JobPriority, BackgroundJobInfo } from '/@/api/task-management/model/backgroundJobInfoModel'; |
||||
|
import { JobTypeMap, JobPriorityMap } from '../datas/typing'; |
||||
|
|
||||
|
const FormItem = Form.Item; |
||||
|
const TabPane = Tabs.TabPane; |
||||
|
|
||||
|
const emit = defineEmits(['change', 'register']); |
||||
|
const { L } = useLocalization('TaskManagement'); |
||||
|
const { ruleCreator } = useValidation(); |
||||
|
const { createMessage } = useMessage(); |
||||
|
const formElRef = ref<any>(); |
||||
|
const activeKey = ref('basic'); |
||||
|
const modelRef = ref<BackgroundJobInfo>({ |
||||
|
id: '', |
||||
|
isEnabled: true, |
||||
|
priority: JobPriority.Normal, |
||||
|
jobType: JobType.Once, |
||||
|
args: {}, |
||||
|
} as BackgroundJobInfo); |
||||
|
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((model) => { |
||||
|
activeKey.value = 'basic'; |
||||
|
fetchModel(model.id); |
||||
|
}); |
||||
|
const [registerParamModal, { openModal: openParamModal, closeModal: closeParamModal }] = useModal(); |
||||
|
const [registerParamForm, { resetFields: resetParamFields, setFieldsValue: setParamFields, validate }] = useForm({ |
||||
|
labelAlign: 'left', |
||||
|
labelWidth: 120, |
||||
|
schemas: [ |
||||
|
{ |
||||
|
field: 'key', |
||||
|
component: 'Input', |
||||
|
label: L('DisplayName:Key'), |
||||
|
required: true, |
||||
|
colProps: { span: 24 }, |
||||
|
componentProps: { |
||||
|
autocomplete: "off" |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
field: 'value', |
||||
|
component: 'InputTextArea', |
||||
|
label: L('DisplayName:Value'), |
||||
|
required: true, |
||||
|
colProps: { span: 24 }, |
||||
|
}, |
||||
|
], |
||||
|
showActionButtonGroup: false, |
||||
|
}); |
||||
|
const columns: BasicColumn[] = [ |
||||
|
{ |
||||
|
title: L('DisplayName:Key'), |
||||
|
dataIndex: 'key', |
||||
|
align: 'left', |
||||
|
width: 200, |
||||
|
sorter: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: L('DisplayName:Value'), |
||||
|
dataIndex: 'value', |
||||
|
align: 'left', |
||||
|
width: 300, |
||||
|
sorter: true, |
||||
|
}, |
||||
|
]; |
||||
|
const [registerTable] = useTable({ |
||||
|
rowKey: 'key', |
||||
|
columns: columns, |
||||
|
pagination: false, |
||||
|
maxHeight: 300, |
||||
|
actionColumn: { |
||||
|
width: 180, |
||||
|
title: L('Actions'), |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}); |
||||
|
const isEditModal = computed(() => { |
||||
|
if (modelRef.value.id) { |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
}); |
||||
|
const modalTitle = computed(() => { |
||||
|
if (!isEditModal.value) { |
||||
|
return L('BackgroundJobs:AddNew'); |
||||
|
} |
||||
|
if (modelRef.value.isAbandoned) { |
||||
|
return `${L('BackgroundJobs:Edit')} - ${L('DisplayName:IsAbandoned')}`; |
||||
|
} |
||||
|
return L('BackgroundJobs:Edit'); |
||||
|
}); |
||||
|
const modalTips = computed(() => { |
||||
|
if (modelRef.value.isAbandoned) { |
||||
|
return L('Description:IsAbandoned'); |
||||
|
} |
||||
|
return ''; |
||||
|
}); |
||||
|
const modelRules = reactive({ |
||||
|
group: ruleCreator.fieldRequired({ |
||||
|
name: 'Group', |
||||
|
resourceName: 'TaskManagement', |
||||
|
prefix: 'DisplayName', |
||||
|
}), |
||||
|
name: ruleCreator.fieldRequired({ |
||||
|
name: 'Name', |
||||
|
resourceName: 'TaskManagement', |
||||
|
prefix: 'DisplayName', |
||||
|
}), |
||||
|
type: ruleCreator.fieldRequired({ |
||||
|
name: 'Type', |
||||
|
resourceName: 'TaskManagement', |
||||
|
prefix: 'DisplayName', |
||||
|
}), |
||||
|
beginTime: ruleCreator.fieldRequired({ |
||||
|
name: 'BeginTime', |
||||
|
resourceName: 'TaskManagement', |
||||
|
prefix: 'DisplayName', |
||||
|
type: 'date', |
||||
|
}), |
||||
|
}); |
||||
|
const jopTypeOptions = reactive([ |
||||
|
{ label: JobTypeMap[JobType.Once], value: JobType.Once }, |
||||
|
{ label: JobTypeMap[JobType.Period], value: JobType.Period }, |
||||
|
{ label: JobTypeMap[JobType.Persistent], value: JobType.Persistent }, |
||||
|
]); |
||||
|
const jobPriorityOptions = reactive([ |
||||
|
{ label: JobPriorityMap[JobPriority.Low], value: JobPriority.Low }, |
||||
|
{ label: JobPriorityMap[JobPriority.BelowNormal], value: JobPriority.BelowNormal }, |
||||
|
{ label: JobPriorityMap[JobPriority.Normal], value: JobPriority.Normal }, |
||||
|
{ label: JobPriorityMap[JobPriority.AboveNormal], value: JobPriority.AboveNormal }, |
||||
|
{ label: JobPriorityMap[JobPriority.High], value: JobPriority.High }, |
||||
|
]); |
||||
|
const jobArgs = computed(() => { |
||||
|
const model = unref(modelRef); |
||||
|
if (!model.args) return []; |
||||
|
return Object.keys(model.args).map((key) => { |
||||
|
return { |
||||
|
key: key, |
||||
|
value: model.args[key], |
||||
|
}; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
function fetchModel(id: string) { |
||||
|
if (!id) { |
||||
|
resetFields(); |
||||
|
return; |
||||
|
} |
||||
|
getById(id).then((res) => { |
||||
|
modelRef.value = res; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function resetFields() { |
||||
|
nextTick(() => { |
||||
|
modelRef.value = { |
||||
|
id: '', |
||||
|
isEnabled: true, |
||||
|
priority: JobPriority.Normal, |
||||
|
jobType: JobType.Once, |
||||
|
args: {}, |
||||
|
} as BackgroundJobInfo; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleSubmit() { |
||||
|
const formEl = unref(formElRef); |
||||
|
formEl?.validate().then(() => { |
||||
|
changeOkLoading(true); |
||||
|
const model = unref(modelRef); |
||||
|
const api = isEditModal.value |
||||
|
? update(model.id, Object.assign(model)) |
||||
|
: create(Object.assign(model)); |
||||
|
api.then(() => { |
||||
|
createMessage.success(L('Successful')); |
||||
|
formEl?.resetFields(); |
||||
|
closeModal(); |
||||
|
emit('change'); |
||||
|
}).finally(() => { |
||||
|
changeOkLoading(false); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleAddNewArg() { |
||||
|
openParamModal(true); |
||||
|
nextTick(() => { |
||||
|
resetParamFields(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleSaveParam() { |
||||
|
validate().then((input) => { |
||||
|
const model = unref(modelRef); |
||||
|
model.args ??= {}; |
||||
|
model.args[input.key] = input.value; |
||||
|
resetParamFields(); |
||||
|
closeParamModal(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleEditParam(record) { |
||||
|
openParamModal(true); |
||||
|
nextTick(() => { |
||||
|
setParamFields(record); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleDeleteParam(record) { |
||||
|
const model = unref(modelRef); |
||||
|
model.args ??= {}; |
||||
|
delete model.args[record.key] |
||||
|
} |
||||
|
</script> |
||||
@ -0,0 +1,167 @@ |
|||||
|
<template> |
||||
|
<div class="content"> |
||||
|
<BasicTable @register="registerTable"> |
||||
|
<template #toolbar> |
||||
|
<a-button |
||||
|
v-if="hasPermission('TaskManagement.BackgroundJobs.Create')" |
||||
|
type="primary" |
||||
|
@click="handleAddNew" |
||||
|
>{{ L('BackgroundJobs:AddNew') }}</a-button |
||||
|
> |
||||
|
</template> |
||||
|
<template #enable="{ record }"> |
||||
|
<Switch :checked="record.isEnabled" disabled /> |
||||
|
</template> |
||||
|
<template #status="{ record }"> |
||||
|
<Tooltip v-if="record.isAbandoned" color="orange" :title="L('Description:IsAbandoned')"> |
||||
|
<Tag :color="JobStatusColor[record.status]">{{ JobStatusMap[record.status] }}</Tag> |
||||
|
</Tooltip> |
||||
|
<Tag v-else :color="JobStatusColor[record.status]">{{ JobStatusMap[record.status] }}</Tag> |
||||
|
</template> |
||||
|
<template #type="{ record }"> |
||||
|
<Tag color="blue">{{ JobTypeMap[record.jobType] }}</Tag> |
||||
|
</template> |
||||
|
<template #priority="{ record }"> |
||||
|
<Tag :color="JobPriorityColor[record.priority]">{{ JobPriorityMap[record.priority] }}</Tag> |
||||
|
</template> |
||||
|
<template #action="{ record }"> |
||||
|
<TableAction |
||||
|
:stop-button-propagation="true" |
||||
|
:actions="[ |
||||
|
{ |
||||
|
auth: 'TaskManagement.BackgroundJobs.Update', |
||||
|
label: L('Edit'), |
||||
|
icon: 'ant-design:edit-outlined', |
||||
|
onClick: handleEdit.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
auth: 'TaskManagement.BackgroundJobs.Delete', |
||||
|
color: 'error', |
||||
|
label: L('Delete'), |
||||
|
icon: 'ant-design:delete-outlined', |
||||
|
onClick: handleDelete.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
:dropDownActions="[ |
||||
|
{ |
||||
|
auth: 'TaskManagement.BackgroundJobs.Pause', |
||||
|
label: L('BackgroundJobs:Pause'), |
||||
|
ifShow: [JobStatus.Running, JobStatus.FailedRetry].includes(record.status), |
||||
|
onClick: handlePause.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
auth: 'TaskManagement.BackgroundJobs.Resume', |
||||
|
label: L('BackgroundJobs:Resume'), |
||||
|
ifShow: [JobStatus.Paused, JobStatus.Stopped].includes(record.status), |
||||
|
onClick: handleResume.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
auth: 'TaskManagement.BackgroundJobs.Trigger', |
||||
|
label: L('BackgroundJobs:Trigger'), |
||||
|
ifShow: [JobStatus.Running, JobStatus.Completed, JobStatus.FailedRetry].includes(record.status), |
||||
|
onClick: handleTrigger.bind(null, record), |
||||
|
}, |
||||
|
{ |
||||
|
auth: 'TaskManagement.BackgroundJobs.Stop', |
||||
|
label: L('BackgroundJobs:Stop'), |
||||
|
ifShow: [JobStatus.Running, JobStatus.FailedRetry].includes(record.status), |
||||
|
onClick: handleStop.bind(null, record), |
||||
|
}, |
||||
|
]" |
||||
|
/> |
||||
|
</template> |
||||
|
</BasicTable> |
||||
|
<BackgroundJobInfoModal @change="handleChange" @register="registerModal" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import { Switch, Modal, Tag, Tooltip, message } from 'ant-design-vue'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
import { usePermission } from '/@/hooks/web/usePermission'; |
||||
|
import { useModal } from '/@/components/Modal'; |
||||
|
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
||||
|
import { formatPagedRequest } from '/@/utils/http/abp/helper'; |
||||
|
import { getList, deleteById, pause, resume, trigger, stop } from '/@/api/task-management/backgroundJobInfo'; |
||||
|
import { JobStatus } from '/@/api/task-management/model/backgroundJobInfoModel'; |
||||
|
import { getDataColumns } from '../datas/TableData'; |
||||
|
import { getSearchFormSchemas } from '../datas/ModalData'; |
||||
|
import { JobStatusMap, JobStatusColor, JobTypeMap, JobPriorityMap, JobPriorityColor } from '../datas/typing'; |
||||
|
import BackgroundJobInfoModal from './BackgroundJobInfoModal.vue'; |
||||
|
|
||||
|
const { L } = useLocalization('TaskManagement'); |
||||
|
const { hasPermission } = usePermission(); |
||||
|
const [registerModal, { openModal }] = useModal(); |
||||
|
const [registerTable, { reload }] = useTable({ |
||||
|
rowKey: 'id', |
||||
|
title: L('BackgroundJobs'), |
||||
|
columns: getDataColumns(), |
||||
|
api: getList, |
||||
|
beforeFetch: formatPagedRequest, |
||||
|
pagination: true, |
||||
|
striped: false, |
||||
|
useSearchForm: true, |
||||
|
showTableSetting: true, |
||||
|
bordered: true, |
||||
|
showIndexColumn: false, |
||||
|
canResize: false, |
||||
|
immediate: true, |
||||
|
rowSelection: { type: 'radio' }, |
||||
|
formConfig: getSearchFormSchemas(), |
||||
|
actionColumn: { |
||||
|
width: 220, |
||||
|
title: L('Actions'), |
||||
|
dataIndex: 'action', |
||||
|
slots: { customRender: 'action' }, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
function handleChange() { |
||||
|
reload(); |
||||
|
} |
||||
|
|
||||
|
function handleAddNew() { |
||||
|
openModal(true, { id: null }); |
||||
|
} |
||||
|
|
||||
|
function handleEdit(record) { |
||||
|
openModal(true, record); |
||||
|
} |
||||
|
|
||||
|
function handlePause(record) { |
||||
|
pause(record.id).then(() => { |
||||
|
message.success(L('Successful')); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleResume(record) { |
||||
|
resume(record.id).then(() => { |
||||
|
message.success(L('Successful')); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleTrigger(record) { |
||||
|
trigger(record.id).then(() => { |
||||
|
message.success(L('Successful')); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleStop(record) { |
||||
|
stop(record.id).then(() => { |
||||
|
message.success(L('Successful')); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleDelete(record) { |
||||
|
Modal.warning({ |
||||
|
title: L('AreYouSure'), |
||||
|
content: L('ItemWillBeDeletedMessage'), |
||||
|
okCancel: true, |
||||
|
onOk: () => { |
||||
|
deleteById(record.id).then(() => { |
||||
|
reload(); |
||||
|
}); |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
@ -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 }, |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
|
} |
||||
@ -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, |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
import { JobStatus, JobType, JobPriority } from '/@/api/task-management/model/backgroundJobInfoModel'; |
||||
|
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
||||
|
|
||||
|
const { L } = useLocalization('TaskManagement'); |
||||
|
|
||||
|
export const JobStatusMap = { |
||||
|
[JobStatus.None]: L('DisplayName:None'), |
||||
|
[JobStatus.Completed]: L('DisplayName:Completed'), |
||||
|
[JobStatus.Running]: L('DisplayName:Running'), |
||||
|
[JobStatus.FailedRetry]: L('DisplayName:FailedRetry'), |
||||
|
[JobStatus.Paused]: L('DisplayName:Paused'), |
||||
|
[JobStatus.Stopped]: L('DisplayName:Stopped'), |
||||
|
} |
||||
|
export const JobStatusColor = { |
||||
|
[JobStatus.None]: '', |
||||
|
[JobStatus.Completed]: '#339933', |
||||
|
[JobStatus.Running]: '#3399CC', |
||||
|
[JobStatus.FailedRetry]: '#FF6600', |
||||
|
[JobStatus.Paused]: '#CC6633', |
||||
|
[JobStatus.Stopped]: '#F00000', |
||||
|
} |
||||
|
|
||||
|
export const JobTypeMap = { |
||||
|
[JobType.Once]: L('DisplayName:Once'), |
||||
|
[JobType.Period]: L('DisplayName:Period'), |
||||
|
[JobType.Persistent]: L('DisplayName:Persistent'), |
||||
|
} |
||||
|
|
||||
|
export const JobPriorityMap = { |
||||
|
[JobPriority.Low]: L('DisplayName:Low'), |
||||
|
[JobPriority.BelowNormal]: L('DisplayName:BelowNormal'), |
||||
|
[JobPriority.Normal]: L('DisplayName:Normal'), |
||||
|
[JobPriority.AboveNormal]: L('DisplayName:AboveNormal'), |
||||
|
[JobPriority.High]: L('DisplayName:High'), |
||||
|
} |
||||
|
|
||||
|
export const JobPriorityColor = { |
||||
|
[JobPriority.Low]: 'purple', |
||||
|
[JobPriority.BelowNormal]: 'cyan', |
||||
|
[JobPriority.Normal]: 'blue', |
||||
|
[JobPriority.AboveNormal]: 'orange', |
||||
|
[JobPriority.High]: 'red', |
||||
|
} |
||||
@ -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> |
||||
@ -0,0 +1,111 @@ |
|||||
|
|
||||
|
Microsoft Visual Studio Solution File, Format Version 12.00 |
||||
|
# Visual Studio Version 17 |
||||
|
VisualStudioVersion = 17.0.31903.59 |
||||
|
MinimumVisualStudioVersion = 10.0.40219.1 |
||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "host", "host", "{8DA8A2EE-0B26-487E-A6C4-518906E92B1B}" |
||||
|
EndProject |
||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C38EB7EF-BAE9-4129-862A-71C652B81775}" |
||||
|
EndProject |
||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{77341F31-F54C-436A-AF8D-F78D91303C45}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Domain.Shared", "modules\task-management\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj", "{691CD138-9FFA-4988-BAC4-A32F0DAE1090}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Domain", "modules\task-management\LINGYUN.Abp.TaskManagement.Domain\LINGYUN.Abp.TaskManagement.Domain.csproj", "{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Application.Contracts", "modules\task-management\LINGYUN.Abp.TaskManagement.Application.Contracts\LINGYUN.Abp.TaskManagement.Application.Contracts.csproj", "{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.Application", "modules\task-management\LINGYUN.Abp.TaskManagement.Application\LINGYUN.Abp.TaskManagement.Application.csproj", "{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.HttpApi", "modules\task-management\LINGYUN.Abp.TaskManagement.HttpApi\LINGYUN.Abp.TaskManagement.HttpApi.csproj", "{283B0039-F67C-41F7-B554-DF5EE9178C4A}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.TaskManagement.EntityFrameworkCore", "modules\task-management\LINGYUN.Abp.TaskManagement.EntityFrameworkCore\LINGYUN.Abp.TaskManagement.EntityFrameworkCore.csproj", "{B8AB5E9B-9711-470E-8072-7444579EC5F6}" |
||||
|
EndProject |
||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C99728F6-FB3C-4D26-8917-1D30725209B9}" |
||||
|
ProjectSection(SolutionItems) = preProject |
||||
|
.editorconfig = .editorconfig |
||||
|
EndProjectSection |
||||
|
EndProject |
||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "provider", "provider", "{385578CC-C0F1-4377-A7A2-682B8F416234}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks", "modules\task-management\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj", "{AC0B4342-9C9B-41E6-9646-E505C763EE77}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks.Quartz", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Quartz\LINGYUN.Abp.BackgroundTasks.Quartz.csproj", "{7051C251-11D0-4971-B13E-F6929AE6DE89}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LY.MicroService.TaskManagement.HttpApi.Host", "services\LY.MicroService.TaskManagement.HttpApi.Host\LY.MicroService.TaskManagement.HttpApi.Host.csproj", "{E8022994-A19F-4540-B9D1-7EF4AA85D18A}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks.Abstractions", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj", "{4A049C32-55F2-4A5F-954A-C8A977C2D87F}" |
||||
|
EndProject |
||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.BackgroundTasks.Jobs", "modules\task-management\LINGYUN.Abp.BackgroundTasks.Jobs\LINGYUN.Abp.BackgroundTasks.Jobs.csproj", "{4C59F590-AA6C-4C46-8060-DA65D8305980}" |
||||
|
EndProject |
||||
|
Global |
||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
||||
|
Debug|Any CPU = Debug|Any CPU |
||||
|
Release|Any CPU = Release|Any CPU |
||||
|
EndGlobalSection |
||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
||||
|
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{691CD138-9FFA-4988-BAC4-A32F0DAE1090}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{283B0039-F67C-41F7-B554-DF5EE9178C4A}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{B8AB5E9B-9711-470E-8072-7444579EC5F6}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{AC0B4342-9C9B-41E6-9646-E505C763EE77}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{7051C251-11D0-4971-B13E-F6929AE6DE89}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{E8022994-A19F-4540-B9D1-7EF4AA85D18A}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{4A049C32-55F2-4A5F-954A-C8A977C2D87F}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
||||
|
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Debug|Any CPU.Build.0 = Debug|Any CPU |
||||
|
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Release|Any CPU.ActiveCfg = Release|Any CPU |
||||
|
{4C59F590-AA6C-4C46-8060-DA65D8305980}.Release|Any CPU.Build.0 = Release|Any CPU |
||||
|
EndGlobalSection |
||||
|
GlobalSection(SolutionProperties) = preSolution |
||||
|
HideSolutionNode = FALSE |
||||
|
EndGlobalSection |
||||
|
GlobalSection(NestedProjects) = preSolution |
||||
|
{691CD138-9FFA-4988-BAC4-A32F0DAE1090} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{8873A651-4F83-43B3-A34E-7AAA8A3ED4CF} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{D5EC4CA0-7E16-4D17-B08E-E162EF332C77} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{09039DDF-5B0E-4670-8055-CC0BE82D4D2C} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{283B0039-F67C-41F7-B554-DF5EE9178C4A} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{B8AB5E9B-9711-470E-8072-7444579EC5F6} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{AC0B4342-9C9B-41E6-9646-E505C763EE77} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{7051C251-11D0-4971-B13E-F6929AE6DE89} = {385578CC-C0F1-4377-A7A2-682B8F416234} |
||||
|
{E8022994-A19F-4540-B9D1-7EF4AA85D18A} = {8DA8A2EE-0B26-487E-A6C4-518906E92B1B} |
||||
|
{4A049C32-55F2-4A5F-954A-C8A977C2D87F} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
{4C59F590-AA6C-4C46-8060-DA65D8305980} = {C38EB7EF-BAE9-4129-862A-71C652B81775} |
||||
|
EndGlobalSection |
||||
|
GlobalSection(ExtensibilityGlobals) = postSolution |
||||
|
SolutionGuid = {E1FD1F4C-D344-408B-97CF-B6F1F6D7D293} |
||||
|
EndGlobalSection |
||||
|
EndGlobal |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Core" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,42 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class AbpBackgroundTaskConcurrentException : AbpJobExecutionException |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="AbpBackgroundTaskConcurrentException"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="innerException">Inner exception</param>
|
||||
|
public AbpBackgroundTaskConcurrentException(Type jobType) |
||||
|
: base( |
||||
|
jobType, |
||||
|
$"This job {jobType.Name} cannot be performed because it has been locked by another performer", |
||||
|
null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="AbpBackgroundTaskConcurrentException"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobType">Execute job type</param>
|
||||
|
/// <param name="innerException">Inner exception</param>
|
||||
|
public AbpBackgroundTaskConcurrentException(Type jobType, Exception innerException) |
||||
|
: base( |
||||
|
jobType, |
||||
|
$"This job {jobType.Name} cannot be performed because it has been locked by another performer", |
||||
|
innerException) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="AbpBackgroundTaskConcurrentException"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobType">Execute job type</param>
|
||||
|
/// <param name="message">Exception message</param>
|
||||
|
/// <param name="innerException">Inner exception</param>
|
||||
|
public AbpBackgroundTaskConcurrentException(Type jobType, string message, Exception innerException) |
||||
|
: base(jobType, message, innerException) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class AbpBackgroundTasksAbstractionsModule : AbpModule |
||||
|
{ |
||||
|
public override void PreConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
AutoAddJobMonitors(context.Services); |
||||
|
} |
||||
|
|
||||
|
private static void AutoAddJobMonitors(IServiceCollection services) |
||||
|
{ |
||||
|
var jobMonitors = new List<Type>(); |
||||
|
|
||||
|
services.OnRegistred(context => |
||||
|
{ |
||||
|
if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(JobEventBase<>))) |
||||
|
{ |
||||
|
jobMonitors.Add(context.ImplementationType); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
services.Configure<AbpBackgroundTasksOptions>(options => |
||||
|
{ |
||||
|
options.JobMonitors.AddIfNotContains(jobMonitors); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class AbpBackgroundTasksOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务监听类型列表
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 用户可以实现事件监听实现自定义逻辑
|
||||
|
/// </remarks>
|
||||
|
public ITypeList<IJobEvent> JobMonitors { get; } |
||||
|
/// <summary>
|
||||
|
/// 作业提供者列表
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 用户实现的作业可以添加在集合中
|
||||
|
/// </remarks>
|
||||
|
public IDictionary<string, Type> JobProviders { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务过期时间
|
||||
|
/// 默认: 15 days
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 任务过期时间,超出时间段清理
|
||||
|
/// </remarks>
|
||||
|
public TimeSpan JobExpiratime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 每次清理任务批次大小
|
||||
|
/// 默认: 1000
|
||||
|
/// </summary>
|
||||
|
public int MaxJobCleanCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 清理过期任务批次Cron表达式
|
||||
|
/// 默认: 600秒(0 0/10 * * * ? * )
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Cron表达式
|
||||
|
/// </remarks>
|
||||
|
public string JobCleanCronExpression { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 每次轮询任务批次大小
|
||||
|
/// 默认: 1000
|
||||
|
/// </summary>
|
||||
|
public int MaxJobFetchCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 轮询任务批次Cron表达式
|
||||
|
/// 默认: 30秒(0/30 * * * * ? )
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Cron表达式
|
||||
|
/// </remarks>
|
||||
|
public string JobFetchCronExpression { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 轮询任务批次时锁定任务超时时长(秒)
|
||||
|
/// 默认:120
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 轮询任务也属于一个后台任务, 需要对每一次轮询加锁,防止重复任务入库
|
||||
|
/// </remarks>
|
||||
|
public int JobFetchLockTimeOut { get; set; } |
||||
|
public AbpBackgroundTasksOptions() |
||||
|
{ |
||||
|
MaxJobFetchCount = 1000; |
||||
|
JobFetchLockTimeOut = 120; |
||||
|
JobFetchCronExpression = "0/30 * * * * ? "; |
||||
|
|
||||
|
MaxJobCleanCount = 1000; |
||||
|
JobExpiratime = TimeSpan.FromDays(15d); |
||||
|
JobCleanCronExpression = "0 0/10 * * * ? *"; |
||||
|
|
||||
|
JobMonitors = new TypeList<IJobEvent>(); |
||||
|
JobProviders = new Dictionary<string, Type>(); |
||||
|
} |
||||
|
|
||||
|
public void AddProvider<TJobRunnable>(string name) |
||||
|
where TJobRunnable : IJobRunnable |
||||
|
{ |
||||
|
JobProviders[name] = typeof(TJobRunnable); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class AbpJobExecutionException : AbpException |
||||
|
{ |
||||
|
public Type JobType { get; } |
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="AbpJobExecutionException"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="innerException">Inner exception</param>
|
||||
|
public AbpJobExecutionException(Type jobType) |
||||
|
: this( |
||||
|
jobType, |
||||
|
$"Unable to execute job {jobType.Name}.", |
||||
|
null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="AbpJobExecutionException"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobType">Execute job type</param>
|
||||
|
/// <param name="innerException">Inner exception</param>
|
||||
|
public AbpJobExecutionException(Type jobType, Exception innerException) |
||||
|
: this( |
||||
|
jobType, |
||||
|
$"Unable to execute job {jobType.Name} because it: {innerException.Message}", |
||||
|
innerException) |
||||
|
{ |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// Creates a new <see cref="AbpJobExecutionException"/> object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobType">Execute job type</param>
|
||||
|
/// <param name="message">Exception message</param>
|
||||
|
/// <param name="innerException">Inner exception</param>
|
||||
|
public AbpJobExecutionException(Type jobType, string message, Exception innerException) |
||||
|
: base(message, innerException) |
||||
|
{ |
||||
|
JobType = jobType; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 挂载任务事件接口
|
||||
|
/// </summary>
|
||||
|
public interface IJobEvent |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务启动前事件
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobEventData"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task OnJobBeforeExecuted(JobEventContext context); |
||||
|
/// <summary>
|
||||
|
/// 任务完成后事件
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobEventData"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task OnJobAfterExecuted(JobEventContext context); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 任务类需要实现此接口
|
||||
|
/// </summary>
|
||||
|
public interface IJobRunnable |
||||
|
{ |
||||
|
Task ExecuteAsync(JobRunnableContext context); |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public abstract class JobEventBase<TEvent> : IJobEvent |
||||
|
{ |
||||
|
public ILogger<TEvent> Logger { protected get; set; } |
||||
|
protected JobEventBase() |
||||
|
{ |
||||
|
Logger = NullLogger<TEvent>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task OnJobAfterExecuted(JobEventContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await OnJobAfterExecutedAsync(context); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError("Failed to execute event, error:" + GetSourceException(ex).Message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task OnJobBeforeExecuted(JobEventContext context) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await OnJobBeforeExecutedAsync(context); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError("Failed to execute preprocessing event, error:" + GetSourceException(ex).Message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual Task OnJobAfterExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual Task OnJobBeforeExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
protected virtual Exception GetSourceException(Exception exception) |
||||
|
{ |
||||
|
if (exception.InnerException != null) |
||||
|
{ |
||||
|
return GetSourceException(exception.InnerException); |
||||
|
} |
||||
|
return exception; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobEventContext |
||||
|
{ |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
public JobEventData EventData { get; } |
||||
|
|
||||
|
public JobEventContext( |
||||
|
IServiceProvider serviceProvider, |
||||
|
JobEventData jobEventData) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
EventData = jobEventData; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobEventData |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public Type Type { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务组别
|
||||
|
/// </summary>
|
||||
|
public string Group { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务标识
|
||||
|
/// </summary>
|
||||
|
public Guid Key { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 执行者租户
|
||||
|
/// </summary>
|
||||
|
public Guid? TenantId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 错误明细
|
||||
|
/// </summary>
|
||||
|
public Exception Exception { get; } |
||||
|
/// <summary>
|
||||
|
/// 任务描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回参数
|
||||
|
/// </summary>
|
||||
|
public string Result { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public int Triggered { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大可执行次数
|
||||
|
/// </summary>
|
||||
|
public int RepeatCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime RunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? LastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 下次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? NextRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public bool IsAbandoned { get; set; } |
||||
|
public JobEventData( |
||||
|
Guid key, |
||||
|
Type type, |
||||
|
string group, |
||||
|
string name, |
||||
|
Exception exception = null) |
||||
|
{ |
||||
|
Key = key; |
||||
|
Type = type; |
||||
|
Group = group; |
||||
|
Name = name; |
||||
|
Exception = exception; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务标识
|
||||
|
/// </summary>
|
||||
|
public Guid Id { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回参数
|
||||
|
/// </summary>
|
||||
|
public string Result { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public IDictionary<string, object> Args { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus Status { get; set; } = JobStatus.None; |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime CreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? LastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 下一次执行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? NextRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public JobType JobType { get; set; } = JobType.Once; |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是周期性任务需要指定
|
||||
|
/// </summary>
|
||||
|
public string Cron { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public int TriggerCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试次数
|
||||
|
/// </summary>
|
||||
|
public int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int MaxTryCount { get; set; } = 50; |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public bool IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public int Interval { get; set; } = 300; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority Priority { get; set; } = JobPriority.Normal; |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public int LockTimeOut { get; set; } |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 与框架保持一致
|
||||
|
/// </remarks>
|
||||
|
public enum JobPriority |
||||
|
{ |
||||
|
Low = 5, |
||||
|
BelowNormal = 10, |
||||
|
Normal = 0xF, |
||||
|
AboveNormal = 20, |
||||
|
High = 25 |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobRunnableContext |
||||
|
{ |
||||
|
public Type JobType { get; } |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
public IReadOnlyDictionary<string, object> JobData { get; } |
||||
|
public object Result { get; private set; } |
||||
|
public JobRunnableContext( |
||||
|
Type jobType, |
||||
|
IServiceProvider serviceProvider, |
||||
|
IReadOnlyDictionary<string, object> jobData) |
||||
|
{ |
||||
|
JobType = jobType; |
||||
|
ServiceProvider = serviceProvider; |
||||
|
JobData = jobData; |
||||
|
} |
||||
|
|
||||
|
public void SetResult(object result) |
||||
|
{ |
||||
|
Result = result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public static class JobRunnableContextExtensions |
||||
|
{ |
||||
|
public static T GetService<T>(this JobRunnableContext context) |
||||
|
{ |
||||
|
return context.ServiceProvider.GetService<T>(); |
||||
|
} |
||||
|
|
||||
|
public static object GetService(this JobRunnableContext context, Type serviceType) |
||||
|
{ |
||||
|
return context.ServiceProvider.GetService(serviceType); |
||||
|
} |
||||
|
|
||||
|
public static T GetRequiredService<T>(this JobRunnableContext context) |
||||
|
{ |
||||
|
return context.ServiceProvider.GetRequiredService<T>(); |
||||
|
} |
||||
|
|
||||
|
public static object GetRequiredService(this JobRunnableContext context, Type serviceType) |
||||
|
{ |
||||
|
return context.ServiceProvider.GetRequiredService(serviceType); |
||||
|
} |
||||
|
|
||||
|
public static string GetString(this JobRunnableContext context, string key) |
||||
|
{ |
||||
|
return context.GetJobData(key).ToString(); |
||||
|
} |
||||
|
|
||||
|
public static bool TryGetString(this JobRunnableContext context, string key, out string value) |
||||
|
{ |
||||
|
if (context.TryGetJobData(key, out var data) && data != null) |
||||
|
{ |
||||
|
value = data.ToString(); |
||||
|
return true; |
||||
|
} |
||||
|
value = default; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static T GetJobData<T>(this JobRunnableContext context, string key) where T : struct |
||||
|
{ |
||||
|
var value = context.GetJobData(key); |
||||
|
|
||||
|
return value.To<T>(); |
||||
|
} |
||||
|
|
||||
|
public static bool TryGetJobData<T>(this JobRunnableContext context, string key, out T value) where T : struct |
||||
|
{ |
||||
|
if (context.TryGetJobData(key, out var data) && data != null) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
value = data.To<T>(); |
||||
|
return true; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
value = default; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static object GetJobData(this JobRunnableContext context, string key) |
||||
|
{ |
||||
|
if (context.TryGetJobData(key, out var value) && value != null) |
||||
|
{ |
||||
|
return value; |
||||
|
} |
||||
|
throw new ArgumentException(key + " not specified."); |
||||
|
} |
||||
|
|
||||
|
public static bool TryGetJobData(this JobRunnableContext context, string key, out object value) |
||||
|
{ |
||||
|
if (context.JobData.TryGetValue(key, out value)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public enum JobStatus |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 未知
|
||||
|
/// </summary>
|
||||
|
None = -1, |
||||
|
/// <summary>
|
||||
|
/// 已完成
|
||||
|
/// </summary>
|
||||
|
Completed = 0, |
||||
|
/// <summary>
|
||||
|
/// 运行中
|
||||
|
/// </summary>
|
||||
|
Running = 10, |
||||
|
/// <summary>
|
||||
|
/// 失败重试
|
||||
|
/// </summary>
|
||||
|
FailedRetry = 15, |
||||
|
/// <summary>
|
||||
|
/// 已暂停
|
||||
|
/// </summary>
|
||||
|
Paused = 20, |
||||
|
/// <summary>
|
||||
|
/// 已停止
|
||||
|
/// </summary>
|
||||
|
Stopped = 30 |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public enum JobType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 一次性
|
||||
|
/// </summary>
|
||||
|
Once, |
||||
|
/// <summary>
|
||||
|
/// 周期性
|
||||
|
/// </summary>
|
||||
|
Period, |
||||
|
/// <summary>
|
||||
|
/// 持续性
|
||||
|
/// </summary>
|
||||
|
Persistent |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Emailing" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Sms" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
||||
|
<ProjectReference Include="..\..\dapr\LINGYUN.Abp.Dapr.Client\LINGYUN.Abp.Dapr.Client.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,27 @@ |
|||||
|
using LINGYUN.Abp.Dapr.Client; |
||||
|
using Volo.Abp.Emailing; |
||||
|
using Volo.Abp.Http.Client; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Sms; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
[DependsOn(typeof(AbpEmailingModule))] |
||||
|
[DependsOn(typeof(AbpSmsModule))] |
||||
|
[DependsOn(typeof(AbpHttpClientModule))] |
||||
|
[DependsOn(typeof(AbpDaprClientModule))] |
||||
|
public class AbpBackgroundTasksJobsModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
Configure<AbpBackgroundTasksOptions>(options => |
||||
|
{ |
||||
|
options.AddProvider<ConsoleJob>(DefaultJobNames.ConsoleJob); |
||||
|
options.AddProvider<SendEmailJob>(DefaultJobNames.SendEmailJob); |
||||
|
options.AddProvider<SendSmsJob>(DefaultJobNames.SendSmsJob); |
||||
|
options.AddProvider<SleepJob>(DefaultJobNames.SleepJob); |
||||
|
options.AddProvider<ServiceInvocationJob>(DefaultJobNames.ServiceInvocationJob); |
||||
|
options.AddProvider<HttpRequestJob>(DefaultJobNames.HttpRequestJob); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public class ConsoleJob : IJobRunnable |
||||
|
{ |
||||
|
public const string PropertyMessage = "message"; |
||||
|
public Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
context.TryGetString(PropertyMessage, out var message); |
||||
|
Console.WriteLine($"This message: {message ?? "None"} comes from the job: {GetType()}"); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public static class DefaultJobNames |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 发送邮件
|
||||
|
/// </summary>
|
||||
|
public const string SendEmailJob = "SendEmail"; |
||||
|
/// <summary>
|
||||
|
/// 发送短信
|
||||
|
/// </summary>
|
||||
|
public const string SendSmsJob = "SendSms"; |
||||
|
/// <summary>
|
||||
|
/// 控制台输出
|
||||
|
/// </summary>
|
||||
|
public const string ConsoleJob = "Console"; |
||||
|
/// <summary>
|
||||
|
/// 休眠
|
||||
|
/// </summary>
|
||||
|
public const string SleepJob = "Sleep"; |
||||
|
/// <summary>
|
||||
|
/// 服务间调用
|
||||
|
/// </summary>
|
||||
|
public const string ServiceInvocationJob = "ServiceInvocation"; |
||||
|
/// <summary>
|
||||
|
/// Http请求
|
||||
|
/// </summary>
|
||||
|
public const string HttpRequestJob = "HttpRequest"; |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Http; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public class HttpRequestJob : IJobRunnable |
||||
|
{ |
||||
|
public const string PropertyUrl = "url"; |
||||
|
public const string PropertyMethod = "method"; |
||||
|
public const string PropertyData = "data"; |
||||
|
public const string PropertyContentType = "contentType"; |
||||
|
public const string PropertyHeaders = "headers"; |
||||
|
public const string PropertyToken = "token"; |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var clientFactory = context.GetRequiredService<IHttpClientFactory>(); |
||||
|
|
||||
|
var client = clientFactory.CreateClient(); |
||||
|
var requestMessage = BuildRequestMessage(context); |
||||
|
|
||||
|
var response = await client.SendAsync( |
||||
|
requestMessage, |
||||
|
HttpCompletionOption.ResponseHeadersRead); |
||||
|
|
||||
|
var stringContent = await response.Content.ReadAsStringAsync(); |
||||
|
|
||||
|
if (!response.IsSuccessStatusCode && stringContent.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
context.SetResult($"HttpStatusCode: {(int)response.StatusCode}, Reason: {response.ReasonPhrase}"); |
||||
|
return; |
||||
|
} |
||||
|
context.SetResult(stringContent); |
||||
|
} |
||||
|
|
||||
|
protected virtual HttpRequestMessage BuildRequestMessage(JobRunnableContext context) |
||||
|
{ |
||||
|
var url = context.GetString(PropertyUrl); |
||||
|
var method = context.GetString(PropertyMethod); |
||||
|
context.TryGetJobData(PropertyData, out var data); |
||||
|
context.TryGetJobData(PropertyContentType, out var contentType); |
||||
|
|
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
|
||||
|
var httpRequestMesasge = new HttpRequestMessage(new HttpMethod(method), url); |
||||
|
if (data != null) |
||||
|
{ |
||||
|
// TODO: 需要支持表单类型
|
||||
|
|
||||
|
// application/json 支持
|
||||
|
httpRequestMesasge.Content = new StringContent( |
||||
|
jsonSerializer.Serialize(data), |
||||
|
Encoding.UTF8, |
||||
|
contentType?.ToString() ?? MimeTypes.Application.Json); |
||||
|
} |
||||
|
if (context.TryGetJobData(PropertyHeaders, out var headers)) |
||||
|
{ |
||||
|
var headersDic = new Dictionary<string, object>(); |
||||
|
if (headers is string headerString) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
headersDic = jsonSerializer.Deserialize<Dictionary<string, object>>(headerString); |
||||
|
} |
||||
|
catch { } |
||||
|
} |
||||
|
foreach (var header in headersDic) |
||||
|
{ |
||||
|
httpRequestMesasge.Headers.Add(header.Key, header.Value.ToString()); |
||||
|
} |
||||
|
} |
||||
|
// TODO: 和 headers 一起?
|
||||
|
if (context.TryGetString(PropertyToken, out var accessToken)) |
||||
|
{ |
||||
|
httpRequestMesasge.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); |
||||
|
} |
||||
|
|
||||
|
return httpRequestMesasge; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Emailing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public class SendEmailJob : IJobRunnable |
||||
|
{ |
||||
|
public const string PropertyFrom = "from"; |
||||
|
public const string PropertyTo = "to"; |
||||
|
public const string PropertySubject = "subject"; |
||||
|
public const string PropertyBody = "body"; |
||||
|
public const string PropertyIsBodyHtml = "isBodyHtml"; |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
context.TryGetString(PropertyFrom, out var from); |
||||
|
var to = context.GetString(PropertyTo); |
||||
|
var subject = context.GetString(PropertySubject); |
||||
|
var body = context.GetString(PropertyBody); |
||||
|
context.TryGetJobData<bool>(PropertyIsBodyHtml, out var isBodyHtml); |
||||
|
|
||||
|
var emailSender = context.GetRequiredService<IEmailSender>(); |
||||
|
|
||||
|
await emailSender.QueueAsync(from, to, subject, body, isBodyHtml); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Sms; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public class SendSmsJob : IJobRunnable |
||||
|
{ |
||||
|
public const string PropertyPhoneNumber = "phoneNumber"; |
||||
|
public const string PropertyMessage = "message"; |
||||
|
public const string PropertyProperties = "properties"; |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var phoneNumber = context.GetString(PropertyPhoneNumber); |
||||
|
var message = context.GetString(PropertyMessage); |
||||
|
|
||||
|
var smsMessage = new SmsMessage(phoneNumber, message); |
||||
|
if (context.TryGetString(PropertyProperties, out var data)) |
||||
|
{ |
||||
|
var properties = new Dictionary<string, object>(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
properties = jsonSerializer.Deserialize<Dictionary<string, object>>(data); |
||||
|
} |
||||
|
catch { } |
||||
|
|
||||
|
smsMessage.Properties.AddIfNotContains(properties); |
||||
|
} |
||||
|
|
||||
|
var smsSender = context.GetRequiredService<ISmsSender>(); |
||||
|
|
||||
|
await smsSender.SendAsync(smsMessage); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,261 @@ |
|||||
|
using LINGYUN.Abp.Dapr.Client.DynamicProxying; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Http.Client; |
||||
|
using Volo.Abp.Http.Client.ClientProxying; |
||||
|
using Volo.Abp.Http.Client.DynamicProxying; |
||||
|
using Volo.Abp.Http.Client.Proxying; |
||||
|
using Volo.Abp.Http.Modeling; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public class ServiceInvocationJob : IJobRunnable |
||||
|
{ |
||||
|
// 必须, 接口类型
|
||||
|
public const string PropertyService = "service"; |
||||
|
// 必须, 接口方法名称
|
||||
|
public const string PropertyMethod = "method"; |
||||
|
// 可选, 请求数据, 传递 Dictionary<string, object> 类型的字符串参数
|
||||
|
public const string PropertyData = "data"; |
||||
|
// 可选, 请求时指定区域性
|
||||
|
public const string PropertyCulture = "culture"; |
||||
|
// 可选, 请求时指定租户
|
||||
|
public const string PropertyTenant = "tenant"; |
||||
|
// 可选, 提供者名称, http、dapr, 无此参数默认 http
|
||||
|
public const string PropertyProvider = "provider"; |
||||
|
// Dapr 调用所必须的参数
|
||||
|
public const string PropertyAppId = "appId"; |
||||
|
|
||||
|
// TODO: support grpc
|
||||
|
private readonly static string[] Providers = new string[] { "http", "dapr" }; |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
// 获取参数列表
|
||||
|
var type = context.GetString(PropertyService); |
||||
|
var method = context.GetString(PropertyMethod); |
||||
|
var serviceType = Type.GetType(type, true); |
||||
|
var serviceMethod = serviceType.GetMethod(method); |
||||
|
context.TryGetString(PropertyCulture, out var culture); |
||||
|
context.TryGetString(PropertyProvider, out var provider); |
||||
|
provider ??= "http"; |
||||
|
|
||||
|
if (!Providers.Contains(provider)) |
||||
|
{ |
||||
|
throw new AbpJobExecutionException( |
||||
|
GetType(), |
||||
|
$"Invalid inter-service invocation provider: {provider}", |
||||
|
null); |
||||
|
} |
||||
|
|
||||
|
Guid? tenantId = null; |
||||
|
if (context.TryGetString(PropertyTenant, out var tenant) && |
||||
|
Guid.TryParse(tenant, out var converTenant)) |
||||
|
{ |
||||
|
tenantId = converTenant; |
||||
|
} |
||||
|
|
||||
|
var currentTenant = context.GetRequiredService<ICurrentTenant>(); |
||||
|
using (currentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
using (CultureHelper.Use(culture ?? CultureInfo.CurrentCulture.Name)) |
||||
|
{ |
||||
|
switch (provider) |
||||
|
{ |
||||
|
case "http": |
||||
|
await ExecuteWithHttpProxy(context, serviceType, serviceMethod); |
||||
|
break; |
||||
|
case "dapr": |
||||
|
await ExecuteWithDaprProxy(context, serviceType, serviceMethod); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#region HttpClient
|
||||
|
|
||||
|
protected readonly static string CallRequestMethod = nameof(DynamicHttpProxyInterceptorClientProxy<object>.CallRequestAsync); |
||||
|
protected readonly static MethodInfo ClientProxyMethod = typeof(DynamicHttpProxyInterceptorClientProxy<>) |
||||
|
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) |
||||
|
.First(m => m.Name == CallRequestMethod && !m.IsGenericMethodDefinition); |
||||
|
protected readonly static MethodInfo CallRequestAsyncMethod = typeof(DynamicHttpProxyInterceptor<object>) |
||||
|
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) |
||||
|
.First(m => m.Name == CallRequestMethod && m.IsGenericMethodDefinition); |
||||
|
|
||||
|
protected virtual async Task ExecuteWithHttpProxy( |
||||
|
JobRunnableContext context, |
||||
|
Type serviceType, |
||||
|
MethodInfo serviceMethod) |
||||
|
{ |
||||
|
// 反射所必须的参数
|
||||
|
var clientProxyType = typeof(DynamicHttpProxyInterceptorClientProxy<>).MakeGenericType(serviceType); |
||||
|
var clientProxy = context.GetRequiredService(clientProxyType); |
||||
|
|
||||
|
// 调用远程服务发现端点
|
||||
|
var actionApiDescription = await GetActionApiDescriptionModel(context, serviceType, serviceMethod); |
||||
|
|
||||
|
// 调用参数
|
||||
|
var invokeParameters = new Dictionary<string, object>(); |
||||
|
if (context.TryGetString(PropertyData, out var data)) |
||||
|
{ |
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
invokeParameters = jsonSerializer.Deserialize<Dictionary<string, object>>(data); |
||||
|
} |
||||
|
|
||||
|
// 构造服务代理上下文
|
||||
|
var clientProxyRequestContext = new ClientProxyRequestContext( |
||||
|
actionApiDescription, |
||||
|
invokeParameters, |
||||
|
serviceType); |
||||
|
|
||||
|
if (serviceMethod.ReturnType.GenericTypeArguments.IsNullOrEmpty()) |
||||
|
{ |
||||
|
// 直接调用
|
||||
|
var taskProxy = (Task)ClientProxyMethod |
||||
|
.Invoke(clientProxy, new object[] { clientProxyRequestContext }); |
||||
|
await taskProxy; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 有返回值的调用
|
||||
|
var returnType = serviceMethod.ReturnType.GenericTypeArguments[0]; |
||||
|
var result = (Task)CallRequestAsyncMethod |
||||
|
.MakeGenericMethod(returnType) |
||||
|
.Invoke(this, new object[] { context }); |
||||
|
|
||||
|
context.SetResult(await GetResultAsync(result, returnType)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel( |
||||
|
JobRunnableContext context, |
||||
|
Type serviceType, |
||||
|
MethodInfo serviceMethod) |
||||
|
{ |
||||
|
var clientOptions = context.GetRequiredService<IOptions<AbpHttpClientOptions>>().Value; |
||||
|
var remoteServiceConfigurationProvider = context.GetRequiredService<IRemoteServiceConfigurationProvider>(); |
||||
|
var proxyHttpClientFactory = context.GetRequiredService<IProxyHttpClientFactory>(); |
||||
|
var apiDescriptionFinder = context.GetRequiredService<IApiDescriptionFinder>(); |
||||
|
|
||||
|
var clientConfig = clientOptions.HttpClientProxies.GetOrDefault(serviceType) ?? |
||||
|
throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {serviceType.FullName}."); |
||||
|
var remoteServiceConfig = await remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); |
||||
|
var client = proxyHttpClientFactory.Create(clientConfig.RemoteServiceName); |
||||
|
|
||||
|
return await apiDescriptionFinder.FindActionAsync( |
||||
|
client, |
||||
|
remoteServiceConfig.BaseUrl, |
||||
|
serviceType, |
||||
|
serviceMethod |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
|
||||
|
#region DaprClient
|
||||
|
|
||||
|
protected readonly static string DaprCallRequestMethod = nameof(DynamicDaprProxyInterceptorClientProxy<object>.CallRequestAsync); |
||||
|
protected readonly static MethodInfo DaprClientProxyMethod = typeof(DynamicDaprProxyInterceptorClientProxy<>) |
||||
|
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) |
||||
|
.First(m => m.Name == DaprCallRequestMethod && !m.IsGenericMethodDefinition); |
||||
|
protected readonly static MethodInfo DaprCallRequestAsyncMethod = typeof(DynamicDaprProxyInterceptorClientProxy<object>) |
||||
|
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) |
||||
|
.First(m => m.Name == DaprCallRequestMethod && m.IsGenericMethodDefinition); |
||||
|
|
||||
|
protected virtual async Task ExecuteWithDaprProxy( |
||||
|
JobRunnableContext context, |
||||
|
Type serviceType, |
||||
|
MethodInfo serviceMethod) |
||||
|
{ |
||||
|
var appid = context.GetString(PropertyAppId); |
||||
|
var clientOptions = context.GetRequiredService<IOptions<AbpHttpClientOptions>>().Value; |
||||
|
var clientConfig = clientOptions.HttpClientProxies.GetOrDefault(serviceType) ?? |
||||
|
throw new AbpJobExecutionException( |
||||
|
GetType(), |
||||
|
$"Could not get DynamicHttpClientProxyConfig for {serviceType.FullName}.", |
||||
|
null); |
||||
|
|
||||
|
// 反射所必须的参数
|
||||
|
var clientProxyType = typeof(DynamicDaprProxyInterceptorClientProxy<>).MakeGenericType(serviceType); |
||||
|
var clientProxy = context.GetRequiredService(clientProxyType); |
||||
|
|
||||
|
// 调用远程服务发现端点
|
||||
|
var actionApiDescription = await GetActionApiDescriptionModel( |
||||
|
context, |
||||
|
clientConfig.RemoteServiceName, |
||||
|
appid, |
||||
|
serviceType, |
||||
|
serviceMethod); |
||||
|
|
||||
|
// 调用参数
|
||||
|
var invokeParameters = new Dictionary<string, object>(); |
||||
|
if (context.TryGetString(PropertyData, out var data)) |
||||
|
{ |
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
invokeParameters = jsonSerializer.Deserialize<Dictionary<string, object>>(data); |
||||
|
} |
||||
|
|
||||
|
// 构造服务代理上下文
|
||||
|
var clientProxyRequestContext = new ClientProxyRequestContext( |
||||
|
actionApiDescription, |
||||
|
invokeParameters, |
||||
|
serviceType); |
||||
|
|
||||
|
if (serviceMethod.ReturnType.GenericTypeArguments.IsNullOrEmpty()) |
||||
|
{ |
||||
|
// 直接调用
|
||||
|
var taskProxy = (Task)DaprClientProxyMethod |
||||
|
.Invoke(clientProxy, new object[] { clientProxyRequestContext }); |
||||
|
await taskProxy; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 有返回值的调用
|
||||
|
var returnType = serviceMethod.ReturnType.GenericTypeArguments[0]; |
||||
|
var result = (Task)DaprCallRequestAsyncMethod |
||||
|
.MakeGenericMethod(returnType) |
||||
|
.Invoke(this, new object[] { context }); |
||||
|
|
||||
|
context.SetResult(await GetResultAsync(result, returnType)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<ActionApiDescriptionModel> GetActionApiDescriptionModel( |
||||
|
JobRunnableContext context, |
||||
|
string service, |
||||
|
string appId, |
||||
|
Type serviceType, |
||||
|
MethodInfo serviceMethod) |
||||
|
{ |
||||
|
var apiDescriptionFinder = context.GetRequiredService<IDaprApiDescriptionFinder>(); |
||||
|
|
||||
|
return await apiDescriptionFinder.FindActionAsync( |
||||
|
service, |
||||
|
appId, |
||||
|
serviceType, |
||||
|
serviceMethod); |
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
protected virtual async Task<object> GetResultAsync(Task task, Type resultType) |
||||
|
{ |
||||
|
await task; |
||||
|
var resultProperty = typeof(Task<>) |
||||
|
.MakeGenericType(resultType) |
||||
|
.GetProperty(nameof(Task<object>.Result), BindingFlags.Instance | BindingFlags.Public); |
||||
|
Check.NotNull(resultProperty, nameof(resultProperty)); |
||||
|
return resultProperty.GetValue(task); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Jobs; |
||||
|
|
||||
|
public class SleepJob : IJobRunnable |
||||
|
{ |
||||
|
public async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
context.JobData.TryGetValue("Delay", out var sleep); |
||||
|
|
||||
|
Console.WriteLine($"Sleep {sleep ?? 20000} milliseconds."); |
||||
|
|
||||
|
await Task.Delay(sleep?.To<int>() ?? 20000); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,19 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Quartz" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Quartz; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Quartz; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
[DependsOn(typeof(AbpBackgroundTasksModule))] |
||||
|
[DependsOn(typeof(AbpQuartzModule))] |
||||
|
public class AbpBackgroundTasksQuartzModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var _scheduler = context.ServiceProvider.GetRequiredService<IScheduler>(); |
||||
|
|
||||
|
_scheduler.ListenerManager.AddJobListener(context.ServiceProvider.GetRequiredService<QuartzJobListener>()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Quartz; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public interface IQuartzJobExecutorProvider |
||||
|
{ |
||||
|
#nullable enable |
||||
|
IJobDetail? CreateJob(JobInfo job); |
||||
|
|
||||
|
ITrigger CreateTrigger(JobInfo job); |
||||
|
#nullable disable |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Quartz; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
[DisallowConcurrentExecution] |
||||
|
public class QuartzJobConcurrentAdapter<TJobRunnable> : QuartzJobSimpleAdapter<TJobRunnable> |
||||
|
where TJobRunnable : IJobRunnable |
||||
|
{ |
||||
|
public QuartzJobConcurrentAdapter(IServiceProvider serviceProvider) |
||||
|
: base(serviceProvider) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,112 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Quartz; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public class QuartzJobExecutorProvider : IQuartzJobExecutorProvider, ISingletonDependency |
||||
|
{ |
||||
|
public ILogger<QuartzJobExecutorProvider> Logger { protected get; set; } |
||||
|
|
||||
|
protected IClock Clock { get; } |
||||
|
protected AbpBackgroundTasksOptions Options { get; } |
||||
|
|
||||
|
public QuartzJobExecutorProvider( |
||||
|
IClock clock, |
||||
|
IOptions<AbpBackgroundTasksOptions> options) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
Logger = NullLogger<QuartzJobExecutorProvider>.Instance; |
||||
|
} |
||||
|
|
||||
|
public IJobDetail CreateJob(JobInfo job) |
||||
|
{ |
||||
|
var jobType = Type.GetType(job.Type) ?? Options.JobProviders.GetOrDefault(job.Type); |
||||
|
if (jobType == null) |
||||
|
{ |
||||
|
Logger.LogWarning($"The task: {job.Group} - {job.Name}: {job.Type} is not registered and cannot create an instance of the performer type."); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var adapterType = typeof(QuartzJobSimpleAdapter<>); |
||||
|
if (job.LockTimeOut > 0) |
||||
|
{ |
||||
|
adapterType = typeof(QuartzJobConcurrentAdapter<>); |
||||
|
} |
||||
|
|
||||
|
if (!typeof(IJob).IsAssignableFrom(jobType)) |
||||
|
{ |
||||
|
jobType = adapterType.MakeGenericType(jobType); |
||||
|
} |
||||
|
|
||||
|
var jobBuilder = JobBuilder.Create(jobType) |
||||
|
.WithIdentity(job.Name, job.Group) |
||||
|
.WithDescription(job.Description); |
||||
|
|
||||
|
jobBuilder.UsingJobData(nameof(JobInfo.Id), job.Id); |
||||
|
jobBuilder.UsingJobData(nameof(JobInfo.LockTimeOut), job.LockTimeOut); |
||||
|
jobBuilder.UsingJobData(new JobDataMap(job.Args)); |
||||
|
|
||||
|
return jobBuilder.Build(); |
||||
|
} |
||||
|
|
||||
|
public ITrigger CreateTrigger(JobInfo job) |
||||
|
{ |
||||
|
var triggerBuilder = TriggerBuilder.Create(); |
||||
|
|
||||
|
switch (job.JobType) |
||||
|
{ |
||||
|
case JobType.Period: |
||||
|
if (!CronExpression.IsValidExpression(job.Cron)) |
||||
|
{ |
||||
|
Logger.LogWarning($"The task: {job.Group} - {job.Name} periodic task Cron expression was invalid and the task trigger could not be created."); |
||||
|
return null; |
||||
|
} |
||||
|
triggerBuilder |
||||
|
.WithIdentity(job.Name, job.Group) |
||||
|
.WithDescription(job.Description) |
||||
|
.EndAt(job.EndTime) |
||||
|
.ForJob(job.Name, job.Group) |
||||
|
.WithPriority((int)job.Priority) |
||||
|
.WithCronSchedule(job.Cron); |
||||
|
if (job.BeginTime > Clock.Now) |
||||
|
{ |
||||
|
triggerBuilder = triggerBuilder.StartAt(job.BeginTime); |
||||
|
} |
||||
|
break; |
||||
|
case JobType.Once: |
||||
|
case JobType.Persistent: |
||||
|
default: |
||||
|
// Quartz 需要减一位
|
||||
|
var maxCount = job.MaxCount <= 0 ? -1 : job.MaxCount - 1; |
||||
|
if (job.JobType == JobType.Once) |
||||
|
{ |
||||
|
maxCount = 0; |
||||
|
} |
||||
|
if (job.Status == JobStatus.FailedRetry && job.TryCount < job.MaxTryCount) |
||||
|
{ |
||||
|
maxCount = job.MaxTryCount <= 0 ? -1 : job.MaxTryCount - 1; |
||||
|
} |
||||
|
triggerBuilder |
||||
|
.WithIdentity(job.Name, job.Group) |
||||
|
.WithDescription(job.Description) |
||||
|
.StartAt(Clock.Now.AddSeconds(job.Interval)) |
||||
|
.EndAt(job.EndTime) |
||||
|
.ForJob(job.Name, job.Group) |
||||
|
.WithPriority((int)job.Priority) |
||||
|
.WithSimpleSchedule(x => |
||||
|
x.WithIntervalInSeconds(job.Interval) |
||||
|
.WithRepeatCount(maxCount)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return triggerBuilder.Build(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,149 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Quartz; |
||||
|
using Quartz.Listener; |
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public class QuartzJobListener : JobListenerSupport, ISingletonDependency |
||||
|
{ |
||||
|
public ILogger<QuartzJobListener> Logger { protected get; set; } |
||||
|
|
||||
|
public override string Name => "QuartzJobListener"; |
||||
|
|
||||
|
protected IJobEventProvider EventProvider { get; } |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public QuartzJobListener( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IJobEventProvider eventProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
EventProvider = eventProvider; |
||||
|
|
||||
|
Logger = NullLogger<QuartzJobListener>.Instance; |
||||
|
} |
||||
|
|
||||
|
public override Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobType = context.JobDetail.JobType; |
||||
|
if (jobType.IsGenericType) |
||||
|
{ |
||||
|
jobType = jobType.GetGenericTypeDefinition(); |
||||
|
} |
||||
|
Logger.LogInformation($"The task {jobType.Name} could not be performed..."); |
||||
|
|
||||
|
return Task.FromResult(-1); |
||||
|
} |
||||
|
|
||||
|
public override async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobId = context.GetString(nameof(JobInfo.Id)); |
||||
|
if (Guid.TryParse(jobId, out var jobUUId)) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var scope = ServiceProvider.CreateScope(); |
||||
|
var jobEventData = new JobEventData( |
||||
|
jobUUId, |
||||
|
context.JobDetail.JobType, |
||||
|
context.JobDetail.Key.Group, |
||||
|
context.JobDetail.Key.Name) |
||||
|
{ |
||||
|
Result = context.Result?.ToString() |
||||
|
}; |
||||
|
|
||||
|
var jobEventList = EventProvider.GetAll(); |
||||
|
var eventContext = new JobEventContext( |
||||
|
scope.ServiceProvider, |
||||
|
jobEventData); |
||||
|
|
||||
|
var index = 0; |
||||
|
var taskList = new Task[jobEventList.Count]; |
||||
|
foreach (var jobEvent in jobEventList) |
||||
|
{ |
||||
|
taskList[index] = jobEvent.OnJobBeforeExecuted(eventContext); |
||||
|
index++; |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(taskList); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError($"The event before the task execution is abnormal:{ex}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
using var scope = ServiceProvider.CreateScope(); |
||||
|
var jobId = context.GetString(nameof(JobInfo.Id)); |
||||
|
if (Guid.TryParse(jobId, out var jobUUId)) |
||||
|
{ |
||||
|
var jobType = context.JobDetail.JobType; |
||||
|
if (jobType.IsGenericType) |
||||
|
{ |
||||
|
jobType = jobType.GetGenericArguments()[0]; |
||||
|
} |
||||
|
|
||||
|
var jobEventData = new JobEventData( |
||||
|
jobUUId, |
||||
|
jobType, |
||||
|
context.JobDetail.Key.Group, |
||||
|
context.JobDetail.Key.Name, |
||||
|
jobException) |
||||
|
{ |
||||
|
Status = JobStatus.Running |
||||
|
}; |
||||
|
|
||||
|
if (context.Trigger is ISimpleTrigger simpleTrigger) |
||||
|
{ |
||||
|
jobEventData.Triggered = simpleTrigger.TimesTriggered; |
||||
|
jobEventData.RepeatCount = simpleTrigger.RepeatCount; |
||||
|
} |
||||
|
jobEventData.Description = context.JobDetail.Description; |
||||
|
jobEventData.RunTime = context.FireTimeUtc.LocalDateTime; |
||||
|
jobEventData.LastRunTime = context.PreviousFireTimeUtc?.LocalDateTime; |
||||
|
jobEventData.NextRunTime = context.NextFireTimeUtc?.LocalDateTime; |
||||
|
if (context.Result != null) |
||||
|
{ |
||||
|
jobEventData.Result = context.Result?.ToString(); |
||||
|
} |
||||
|
var tenantIdString = context.GetString(nameof(IMultiTenant.TenantId)); |
||||
|
if (Guid.TryParse(tenantIdString, out var tenantId)) |
||||
|
{ |
||||
|
jobEventData.TenantId = tenantId; |
||||
|
} |
||||
|
|
||||
|
var jobEventList = EventProvider.GetAll(); |
||||
|
var eventContext = new JobEventContext( |
||||
|
scope.ServiceProvider, |
||||
|
jobEventData); |
||||
|
|
||||
|
var index = 0; |
||||
|
var taskList = new Task[jobEventList.Count]; |
||||
|
foreach (var jobEvent in jobEventList) |
||||
|
{ |
||||
|
taskList[index] = jobEvent.OnJobAfterExecuted(eventContext); |
||||
|
index++; |
||||
|
} |
||||
|
|
||||
|
await Task.WhenAll(taskList); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
Logger.LogError($"The event is abnormal after the task is executed:{ex}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,163 @@ |
|||||
|
using Quartz; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class QuartzJobScheduler : IJobScheduler, ISingletonDependency |
||||
|
{ |
||||
|
protected IJobStore JobStore { get; } |
||||
|
protected IScheduler Scheduler { get; } |
||||
|
protected IQuartzJobExecutorProvider QuartzJobExecutor { get; } |
||||
|
|
||||
|
public QuartzJobScheduler( |
||||
|
IJobStore jobStore, |
||||
|
IScheduler scheduler, |
||||
|
IQuartzJobExecutorProvider quartzJobExecutor) |
||||
|
{ |
||||
|
JobStore = jobStore; |
||||
|
Scheduler = scheduler; |
||||
|
QuartzJobExecutor = quartzJobExecutor; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> ExistsAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
return await Scheduler.CheckExists(jobKey); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task PauseAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
var triggers = await Scheduler.GetTriggersOfJob(jobKey); |
||||
|
foreach (var trigger in triggers) |
||||
|
{ |
||||
|
await Scheduler.PauseTrigger(trigger.Key); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> QueueAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var jobDetail = QuartzJobExecutor.CreateJob(job); |
||||
|
if (jobDetail == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var jobTrigger = QuartzJobExecutor.CreateTrigger(job); |
||||
|
if (jobTrigger == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
await Scheduler.ScheduleJob(jobDetail, jobTrigger); |
||||
|
|
||||
|
return await Scheduler.CheckExists(jobTrigger.Key); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task QueuesAsync(IEnumerable<JobInfo> jobs) |
||||
|
{ |
||||
|
var jobDictionary = new Dictionary<IJobDetail, IReadOnlyCollection<ITrigger>>(); |
||||
|
foreach (var job in jobs) |
||||
|
{ |
||||
|
var jobDetail = QuartzJobExecutor.CreateJob(job); |
||||
|
if (jobDetail == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var jobTrigger = QuartzJobExecutor.CreateTrigger(job); |
||||
|
if (jobTrigger == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
jobDictionary.Add(jobDetail, new ITrigger[] { jobTrigger }); |
||||
|
} |
||||
|
|
||||
|
await Scheduler.ScheduleJobs(jobDictionary, false); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> RemoveAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (!await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var triggers = await Scheduler.GetTriggersOfJob(jobKey); |
||||
|
foreach (var trigger in triggers) |
||||
|
{ |
||||
|
await Scheduler.PauseTrigger(trigger.Key); |
||||
|
} |
||||
|
await Scheduler.DeleteJob(jobKey); |
||||
|
|
||||
|
return !await Scheduler.CheckExists(jobKey); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ResumeAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
var triggers = await Scheduler.GetTriggersOfJob(jobKey); |
||||
|
foreach (var trigger in triggers) |
||||
|
{ |
||||
|
await Scheduler.ResumeTrigger(trigger.Key); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> ShutdownAsync() |
||||
|
{ |
||||
|
await StopAsync(); |
||||
|
|
||||
|
await Scheduler.Shutdown(true); |
||||
|
|
||||
|
return Scheduler.IsShutdown; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> StartAsync() |
||||
|
{ |
||||
|
if (Scheduler.InStandbyMode) |
||||
|
{ |
||||
|
await Scheduler.Start(); |
||||
|
} |
||||
|
return Scheduler.InStandbyMode; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<bool> StopAsync() |
||||
|
{ |
||||
|
if (!Scheduler.InStandbyMode) |
||||
|
{ |
||||
|
//等待任务运行完成
|
||||
|
await Scheduler.Standby(); |
||||
|
} |
||||
|
return !Scheduler.InStandbyMode; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task TriggerAsync(JobInfo job) |
||||
|
{ |
||||
|
var jobKey = new JobKey(job.Name, job.Group); |
||||
|
if (!await Scheduler.CheckExists(jobKey)) |
||||
|
{ |
||||
|
job.JobType = JobType.Once; |
||||
|
|
||||
|
await QueueAsync(job); |
||||
|
} |
||||
|
await Scheduler.TriggerJob(jobKey); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Quartz; |
||||
|
using System; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Quartz; |
||||
|
|
||||
|
public class QuartzJobSimpleAdapter<TJobRunnable> : IJob |
||||
|
where TJobRunnable : IJobRunnable |
||||
|
{ |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public QuartzJobSimpleAdapter( |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async virtual Task Execute(IJobExecutionContext context) |
||||
|
{ |
||||
|
// 任务已经在一个作用域中
|
||||
|
// using var scope = ServiceProvider.CreateScope();
|
||||
|
var jobExecuter = ServiceProvider.GetRequiredService<IJobRunnableExecuter>(); |
||||
|
|
||||
|
var jobContext = new JobRunnableContext( |
||||
|
typeof(TJobRunnable), |
||||
|
ServiceProvider, |
||||
|
context.MergedJobDataMap.ToImmutableDictionary()); |
||||
|
|
||||
|
await jobExecuter.ExecuteAsync(jobContext); |
||||
|
|
||||
|
context.Result = jobContext.Result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Quartz; |
||||
|
|
||||
|
public static class IJobExecutionContextExtensions |
||||
|
{ |
||||
|
public static TValue GetData<TValue>(this IJobExecutionContext context, string key) |
||||
|
{ |
||||
|
var value = context.MergedJobDataMap.GetString(key); |
||||
|
|
||||
|
return (TValue)Convert.ChangeType(value, typeof(TValue)); |
||||
|
} |
||||
|
|
||||
|
public static string GetString(this IJobExecutionContext context, string key) |
||||
|
{ |
||||
|
var value = context.MergedJobDataMap.Get(key); |
||||
|
|
||||
|
return value != null ? value.ToString() : ""; |
||||
|
} |
||||
|
|
||||
|
public static int GetInt(this IJobExecutionContext context, string key) |
||||
|
{ |
||||
|
var value = context.MergedJobDataMap.GetInt(key); |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
# LINGYUN.Abp.BackgroundTasks.Quartz |
||||
|
|
||||
|
后台任务(队列)模块的Quartz实现. |
||||
|
并添加一个监听器用于通知管理者任务状态 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
模块按需引用,具体配置参考Volo.Abp.Quartz模块 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpBackgroundTasksQuartzModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Auditing" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.BackgroundJobs.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.DistributedLocking.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Guids" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.BackgroundTasks.Abstractions\LINGYUN.Abp.BackgroundTasks.Abstractions.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,23 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.DistributedLocking; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
[DependsOn(typeof(AbpAuditingModule))] |
||||
|
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))] |
||||
|
[DependsOn(typeof(AbpBackgroundJobsAbstractionsModule))] |
||||
|
[DependsOn(typeof(AbpDistributedLockingAbstractionsModule))] |
||||
|
[DependsOn(typeof(AbpGuidsModule))] |
||||
|
public class AbpBackgroundTasksModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTransient(typeof(BackgroundJobAdapter<>)); |
||||
|
context.Services.AddHostedService<DefaultBackgroundWorker>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.Json; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class BackgroundJobAdapter<TArgs> : IJobRunnable |
||||
|
{ |
||||
|
public ILogger<BackgroundJobAdapter<TArgs>> Logger { protected get; set; } |
||||
|
|
||||
|
protected AbpBackgroundJobOptions Options { get; } |
||||
|
protected IServiceScopeFactory ServiceScopeFactory { get; } |
||||
|
protected IBackgroundJobExecuter JobExecuter { get; } |
||||
|
|
||||
|
public BackgroundJobAdapter( |
||||
|
IOptions<AbpBackgroundJobOptions> options, |
||||
|
IBackgroundJobExecuter jobExecuter, |
||||
|
IServiceScopeFactory serviceScopeFactory) |
||||
|
{ |
||||
|
JobExecuter = jobExecuter; |
||||
|
ServiceScopeFactory = serviceScopeFactory; |
||||
|
Options = options.Value; |
||||
|
|
||||
|
Logger = NullLogger<BackgroundJobAdapter<TArgs>>.Instance; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
using var scope = ServiceScopeFactory.CreateScope(); |
||||
|
object args = null; |
||||
|
if (context.TryGetString(nameof(TArgs), out var argsJson)) |
||||
|
{ |
||||
|
var jsonSerializer = context.GetRequiredService<IJsonSerializer>(); |
||||
|
args = jsonSerializer.Deserialize<TArgs>(argsJson); |
||||
|
} |
||||
|
|
||||
|
var jobType = Options.GetJob(typeof(TArgs)).JobType; |
||||
|
var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args); |
||||
|
await JobExecuter.ExecuteAsync(jobContext); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,94 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.BackgroundJobs; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.Json; |
||||
|
using Volo.Abp.Timing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class BackgroundJobManager : IBackgroundJobManager, ITransientDependency |
||||
|
{ |
||||
|
protected IClock Clock { get; } |
||||
|
protected IJobStore JobStore { get; } |
||||
|
protected IJobScheduler JobScheduler { get; } |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
protected IJsonSerializer JsonSerializer { get; } |
||||
|
protected AbpBackgroundJobOptions Options { get; } |
||||
|
public BackgroundJobManager( |
||||
|
IClock clock, |
||||
|
IJobStore jobStore, |
||||
|
IJobScheduler jobScheduler, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IJsonSerializer jsonSerializer, |
||||
|
IOptions<AbpBackgroundJobOptions> options) |
||||
|
{ |
||||
|
Clock = clock; |
||||
|
JobStore = jobStore; |
||||
|
JobScheduler = jobScheduler; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
JsonSerializer = jsonSerializer; |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> EnqueueAsync<TArgs>( |
||||
|
TArgs args, |
||||
|
BackgroundJobPriority priority = BackgroundJobPriority.Normal, |
||||
|
TimeSpan? delay = null) |
||||
|
{ |
||||
|
var jobConfiguration = Options.GetJob<TArgs>(); |
||||
|
var interval = 0; |
||||
|
if (delay.HasValue) |
||||
|
{ |
||||
|
interval = delay.Value.Seconds; |
||||
|
} |
||||
|
var jobId = GuidGenerator.Create(); |
||||
|
var jobArgs = new Dictionary<string, object> |
||||
|
{ |
||||
|
{ nameof(TArgs), JsonSerializer.Serialize(args) }, |
||||
|
{ "ArgsType", jobConfiguration.ArgsType.AssemblyQualifiedName }, |
||||
|
{ "JobType", jobConfiguration.JobType.AssemblyQualifiedName }, |
||||
|
{ "JobName", jobConfiguration.JobName }, |
||||
|
}; |
||||
|
var jobInfo = new JobInfo |
||||
|
{ |
||||
|
Id = jobId, |
||||
|
Name = jobId.ToString(), |
||||
|
Group = "BackgroundJobs", |
||||
|
Priority = ConverForm(priority), |
||||
|
BeginTime = DateTime.Now, |
||||
|
Args = jobArgs, |
||||
|
Description = "From the framework background jobs", |
||||
|
JobType = JobType.Once, |
||||
|
Interval = interval, |
||||
|
CreationTime = Clock.Now, |
||||
|
// 确保不会被轮询入队
|
||||
|
Status = JobStatus.None, |
||||
|
Type = typeof(BackgroundJobAdapter<TArgs>).AssemblyQualifiedName, |
||||
|
}; |
||||
|
|
||||
|
// 存储状态
|
||||
|
await JobStore.StoreAsync(jobInfo); |
||||
|
|
||||
|
// 手动入队
|
||||
|
await JobScheduler.QueueAsync(jobInfo); |
||||
|
|
||||
|
return jobId.ToString(); |
||||
|
} |
||||
|
|
||||
|
private JobPriority ConverForm(BackgroundJobPriority priority) |
||||
|
{ |
||||
|
return priority switch |
||||
|
{ |
||||
|
BackgroundJobPriority.Low => JobPriority.Low, |
||||
|
BackgroundJobPriority.High => JobPriority.High, |
||||
|
BackgroundJobPriority.BelowNormal => JobPriority.BelowNormal, |
||||
|
BackgroundJobPriority.AboveNormal => JobPriority.AboveNormal, |
||||
|
_ => JobPriority.Normal, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 任务事件提供者
|
||||
|
/// </summary>
|
||||
|
public interface IJobEventProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 返回所有任务事件注册接口
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
IReadOnlyCollection<IJobEvent> GetAll(); |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 定义任务执行者接口
|
||||
|
/// 可以通过它实现一些限制(例如分布式锁)
|
||||
|
/// </summary>
|
||||
|
public interface IJobRunnableExecuter |
||||
|
{ |
||||
|
Task ExecuteAsync(JobRunnableContext context); |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
/// <summary>
|
||||
|
/// 作业调度接口
|
||||
|
/// </summary>
|
||||
|
public interface IJobScheduler |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务入队
|
||||
|
/// </summary>
|
||||
|
/// <param name="job"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> QueueAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 任务入队
|
||||
|
/// </summary>
|
||||
|
/// <param name="jobs"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task QueuesAsync(IEnumerable<JobInfo> jobs); |
||||
|
/// <summary>
|
||||
|
/// 任务是否存在
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> ExistsAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 触发任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task TriggerAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 暂停任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task PauseAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 恢复暂停的任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task ResumeAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 移除任务
|
||||
|
/// </summary>
|
||||
|
/// <param name="group"></param>
|
||||
|
/// <param name="name"></param>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> RemoveAsync(JobInfo job); |
||||
|
/// <summary>
|
||||
|
/// 启动任务协调器
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> StartAsync(); |
||||
|
/// <summary>
|
||||
|
/// 停止任务协调器
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> StopAsync(); |
||||
|
/// <summary>
|
||||
|
/// 释放任务协调器
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
Task<bool> ShutdownAsync(); |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public interface IJobStore |
||||
|
{ |
||||
|
Task<List<JobInfo>> GetWaitingListAsync( |
||||
|
int maxResultCount, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<JobInfo>> GetAllPeriodTasksAsync( |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<JobInfo> FindAsync(Guid jobId); |
||||
|
|
||||
|
Task StoreAsync(JobInfo jobInfo); |
||||
|
|
||||
|
Task StoreLogAsync(JobEventData eventData); |
||||
|
|
||||
|
Task CleanupAsync( |
||||
|
int maxResultCount, |
||||
|
TimeSpan jobExpiratime, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[DisableAuditing] |
||||
|
internal class BackgroundCleaningJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value; |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
await store.CleanupAsync( |
||||
|
options.MaxJobCleanCount, |
||||
|
options.JobExpiratime); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[DisableAuditing] |
||||
|
internal class BackgroundKeepAliveJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
// TODO: 如果积压有大量周期性任务, 可能后面的队列无法被检索到
|
||||
|
var periodJobs = await store.GetAllPeriodTasksAsync(); |
||||
|
|
||||
|
if (!periodJobs.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
|
||||
|
await jobScheduler.QueuesAsync(periodJobs); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[DisableAuditing] |
||||
|
internal class BackgroundPollingJob : IJobRunnable |
||||
|
{ |
||||
|
public virtual async Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundTasksOptions>>().Value; |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
// TODO: 如果积压有大量持续性任务, 可能后面的队列无法被检索到
|
||||
|
// 尽量让任务重复次数在可控范围内
|
||||
|
// 需要借助队列提供者来持久化已入队任务
|
||||
|
var waitingJobs = await store.GetWaitingListAsync(options.MaxJobFetchCount); |
||||
|
|
||||
|
if (!waitingJobs.Any()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
|
||||
|
foreach (var job in waitingJobs) |
||||
|
{ |
||||
|
await jobScheduler.QueueAsync(job); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
internal class DefaultBackgroundWorker : BackgroundService |
||||
|
{ |
||||
|
private readonly IJobStore _jobStore; |
||||
|
private readonly IJobScheduler _jobScheduler; |
||||
|
private readonly AbpBackgroundTasksOptions _options; |
||||
|
|
||||
|
public DefaultBackgroundWorker( |
||||
|
IJobStore jobStore, |
||||
|
IJobScheduler jobScheduler, |
||||
|
IOptions<AbpBackgroundTasksOptions> options) |
||||
|
{ |
||||
|
_jobStore = jobStore; |
||||
|
_jobScheduler = jobScheduler; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
protected async override Task ExecuteAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
await QueuePollingJob(); |
||||
|
await QueueKeepAliveJob(); |
||||
|
await QueueCleaningJob(); |
||||
|
} |
||||
|
|
||||
|
private async Task QueueKeepAliveJob() |
||||
|
{ |
||||
|
var keepAliveJob = BuildKeepAliveJobInfo(); |
||||
|
await _jobScheduler.QueueAsync(keepAliveJob); |
||||
|
} |
||||
|
|
||||
|
private async Task QueuePollingJob() |
||||
|
{ |
||||
|
var pollingJob = BuildPollingJobInfo(); |
||||
|
await _jobScheduler.QueueAsync(pollingJob); |
||||
|
} |
||||
|
|
||||
|
private async Task QueueCleaningJob() |
||||
|
{ |
||||
|
var cleaningJob = BuildCleaningJobInfo(); |
||||
|
await _jobScheduler.QueueAsync(cleaningJob); |
||||
|
} |
||||
|
|
||||
|
private JobInfo BuildKeepAliveJobInfo() |
||||
|
{ |
||||
|
return new JobInfo |
||||
|
{ |
||||
|
Id = Guid.Parse("8F50C5D9-5691-4B99-A52B-CABD91D93C89"), |
||||
|
Name = nameof(BackgroundKeepAliveJob), |
||||
|
Group = "Default", |
||||
|
Description = "Add periodic tasks", |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Status = JobStatus.Running, |
||||
|
BeginTime = DateTime.Now, |
||||
|
CreationTime = DateTime.Now, |
||||
|
JobType = JobType.Once, |
||||
|
Priority = JobPriority.High, |
||||
|
MaxCount = 1, |
||||
|
Interval = 30, |
||||
|
Type = typeof(BackgroundKeepAliveJob).AssemblyQualifiedName, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private JobInfo BuildPollingJobInfo() |
||||
|
{ |
||||
|
return new JobInfo |
||||
|
{ |
||||
|
Id = Guid.Parse("C51152E9-F0B8-4252-8352-283BE46083CC"), |
||||
|
Name = nameof(BackgroundPollingJob), |
||||
|
Group = "Default", |
||||
|
Description = "Polling tasks to be executed", |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Status = JobStatus.Running, |
||||
|
BeginTime = DateTime.Now, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Cron = _options.JobFetchCronExpression, |
||||
|
JobType = JobType.Period, |
||||
|
Priority = JobPriority.High, |
||||
|
LockTimeOut = _options.JobFetchLockTimeOut, |
||||
|
Type = typeof(BackgroundPollingJob).AssemblyQualifiedName, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private JobInfo BuildCleaningJobInfo() |
||||
|
{ |
||||
|
return new JobInfo |
||||
|
{ |
||||
|
Id = Guid.Parse("AAAF8783-FA06-4CF9-BDCA-11140FB2478F"), |
||||
|
Name = nameof(BackgroundCleaningJob), |
||||
|
Group = "Default", |
||||
|
Description = "Cleaning tasks to be executed", |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Status = JobStatus.Running, |
||||
|
BeginTime = DateTime.Now, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Cron = _options.JobCleanCronExpression, |
||||
|
JobType = JobType.Period, |
||||
|
Priority = JobPriority.High, |
||||
|
Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
[Dependency(TryRegister = true)] |
||||
|
internal class InMemoryJobStore : IJobStore, ISingletonDependency |
||||
|
{ |
||||
|
private readonly List<JobInfo> _memoryJobStore; |
||||
|
|
||||
|
public InMemoryJobStore() |
||||
|
{ |
||||
|
_memoryJobStore = new List<JobInfo>(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<JobInfo>> GetAllPeriodTasksAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var jobs = _memoryJobStore |
||||
|
.Where(x => x.JobType == JobType.Period && x.Status == JobStatus.Running) |
||||
|
.OrderByDescending(x => x.Priority) |
||||
|
.ToList(); |
||||
|
|
||||
|
return Task.FromResult(jobs); |
||||
|
} |
||||
|
|
||||
|
public Task<List<JobInfo>> GetWaitingListAsync(int maxResultCount, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var now = DateTime.Now; |
||||
|
var jobs = _memoryJobStore |
||||
|
.Where(x => !x.IsAbandoned && x.JobType != JobType.Period && x.Status == JobStatus.Running) |
||||
|
.OrderByDescending(x => x.Priority) |
||||
|
.ThenBy(x => x.TryCount) |
||||
|
.ThenBy(x => x.NextRunTime) |
||||
|
.Take(maxResultCount) |
||||
|
.ToList(); |
||||
|
|
||||
|
return Task.FromResult(jobs); |
||||
|
} |
||||
|
|
||||
|
public Task<JobInfo> FindAsync(Guid jobId) |
||||
|
{ |
||||
|
var job = _memoryJobStore.FirstOrDefault(x => x.Id.Equals(jobId)); |
||||
|
return Task.FromResult(job); |
||||
|
} |
||||
|
|
||||
|
public Task StoreAsync(JobInfo jobInfo) |
||||
|
{ |
||||
|
var job = _memoryJobStore.FirstOrDefault(x => x.Id.Equals(jobInfo.Id)); |
||||
|
if (job != null) |
||||
|
{ |
||||
|
job.NextRunTime = jobInfo.NextRunTime; |
||||
|
job.LastRunTime = jobInfo.LastRunTime; |
||||
|
job.Status = jobInfo.Status; |
||||
|
job.TriggerCount = jobInfo.TriggerCount; |
||||
|
job.TryCount = jobInfo.TryCount; |
||||
|
job.IsAbandoned = jobInfo.IsAbandoned; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_memoryJobStore.Add(jobInfo); |
||||
|
} |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task StoreLogAsync(JobEventData eventData) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task CleanupAsync(int maxResultCount, TimeSpan jobExpiratime, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var expiratime = DateTime.Now - jobExpiratime; |
||||
|
|
||||
|
var expriaJobs = _memoryJobStore.Where( |
||||
|
x => x.Status == JobStatus.Completed && |
||||
|
expiratime.CompareTo(x.LastRunTime ?? x.EndTime ?? x.CreationTime) <= 0) |
||||
|
.Take(maxResultCount); |
||||
|
|
||||
|
_memoryJobStore.RemoveAll(expriaJobs); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
internal class JobEventProvider : IJobEventProvider, ISingletonDependency |
||||
|
{ |
||||
|
private readonly Lazy<List<IJobEvent>> _lazyEvents; |
||||
|
private List<IJobEvent> _events => _lazyEvents.Value; |
||||
|
|
||||
|
private readonly IServiceProvider _serviceProvider; |
||||
|
private readonly AbpBackgroundTasksOptions _options; |
||||
|
public JobEventProvider( |
||||
|
IOptions<AbpBackgroundTasksOptions> options, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
_options = options.Value; |
||||
|
_serviceProvider = serviceProvider; |
||||
|
|
||||
|
_lazyEvents = new Lazy<List<IJobEvent>>(CreateJobEvents); |
||||
|
} |
||||
|
public IReadOnlyCollection<IJobEvent> GetAll() |
||||
|
{ |
||||
|
return _events.ToImmutableList(); |
||||
|
} |
||||
|
|
||||
|
private List<IJobEvent> CreateJobEvents() |
||||
|
{ |
||||
|
var jobEvents = _options |
||||
|
.JobMonitors |
||||
|
.Select(p => _serviceProvider.GetRequiredService(p) as IJobEvent) |
||||
|
.ToList(); |
||||
|
|
||||
|
return jobEvents; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
public class JobExecutedEvent : JobEventBase<JobExecutedEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected override async Task OnJobAfterExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
var job = await store.FindAsync(context.EventData.Key); |
||||
|
if (job != null) |
||||
|
{ |
||||
|
job.TriggerCount += 1; |
||||
|
job.LastRunTime = context.EventData.RunTime; |
||||
|
job.NextRunTime = context.EventData.NextRunTime; |
||||
|
job.Result = context.EventData.Result ?? "OK"; |
||||
|
job.Status = JobStatus.Running; |
||||
|
|
||||
|
// 一次性任务执行一次后标记为已完成
|
||||
|
if (job.JobType == JobType.Once) |
||||
|
{ |
||||
|
job.Status = JobStatus.Completed; |
||||
|
} |
||||
|
|
||||
|
// 任务异常后可重试
|
||||
|
if (context.EventData.Exception != null) |
||||
|
{ |
||||
|
job.TryCount += 1; |
||||
|
job.IsAbandoned = false; |
||||
|
// 将任务标记为运行中, 会被轮询重新进入队列
|
||||
|
job.Status = JobStatus.FailedRetry; |
||||
|
job.Result = context.EventData.Exception.Message; |
||||
|
|
||||
|
// 多次异常后需要重新计算优先级
|
||||
|
if (job.TryCount <= (job.MaxTryCount / 2) && |
||||
|
job.TryCount > (job.MaxTryCount / 3)) |
||||
|
{ |
||||
|
job.Priority = JobPriority.BelowNormal; |
||||
|
} |
||||
|
else if (job.TryCount > (job.MaxTryCount / 1.5)) |
||||
|
{ |
||||
|
job.Priority = JobPriority.Low; |
||||
|
} |
||||
|
|
||||
|
if (job.TryCount > job.MaxTryCount) |
||||
|
{ |
||||
|
job.Status = JobStatus.Stopped; |
||||
|
job.IsAbandoned = true; |
||||
|
job.NextRunTime = null; |
||||
|
|
||||
|
await RemoveJobAsync(context, job); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 所有任务达到上限则标记已完成
|
||||
|
if (job.MaxCount > 0 && job.TriggerCount >= job.MaxCount) |
||||
|
{ |
||||
|
job.Status = JobStatus.Completed; |
||||
|
job.NextRunTime = null; |
||||
|
|
||||
|
await RemoveJobAsync(context, job); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await store.StoreAsync(job); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task RemoveJobAsync(JobEventContext context, JobInfo jobInfo) |
||||
|
{ |
||||
|
var jobScheduler = context.ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
await jobScheduler.RemoveAsync(jobInfo); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Auditing; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks.Internal; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 存储任务日志
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// 任务类型标记了<see cref="DisableAuditingAttribute"/> 特性则不会记录日志
|
||||
|
/// </remarks>
|
||||
|
public class JobLogEvent : JobEventBase<JobLogEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected async override Task OnJobAfterExecutedAsync(JobEventContext context) |
||||
|
{ |
||||
|
if (context.EventData.Type.IsDefined(typeof(DisableAuditingAttribute), true)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
var store = context.ServiceProvider.GetRequiredService<IJobStore>(); |
||||
|
|
||||
|
await store.StoreLogAsync(context.EventData); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DistributedLocking; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.BackgroundTasks; |
||||
|
|
||||
|
public class JobRunnableExecuter : IJobRunnableExecuter, ISingletonDependency |
||||
|
{ |
||||
|
protected const string LockKeyFormat = "p:{0},job:{1},key:{2}"; |
||||
|
|
||||
|
public async virtual Task ExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
Guid? tenantId = null; |
||||
|
if (context.JobData.TryGetValue(nameof(IMultiTenant.TenantId), out var tenant) && |
||||
|
Guid.TryParse(tenant?.ToString(), out var tid)) |
||||
|
{ |
||||
|
tenantId = tid; |
||||
|
} |
||||
|
|
||||
|
var currentTenant = context.ServiceProvider.GetRequiredService<ICurrentTenant>(); |
||||
|
using (currentTenant.Change(tenantId)) |
||||
|
{ |
||||
|
context.JobData.TryGetValue(nameof(JobInfo.LockTimeOut), out var lockTime); |
||||
|
|
||||
|
// 某些提供者如果无法保证锁一致性, 那么需要用分布式锁
|
||||
|
if (lockTime != null && (lockTime is int time && time > 0)) |
||||
|
{ |
||||
|
var jobId = context.JobData.GetOrDefault(nameof(JobInfo.Id)); |
||||
|
var jobLockKey = string.Format(LockKeyFormat, tenantId?.ToString() ?? "Default", context.JobType.Name, jobId); |
||||
|
var distributedLock = context.ServiceProvider.GetRequiredService<IAbpDistributedLock>(); |
||||
|
|
||||
|
var handle = await distributedLock.TryAcquireAsync(jobLockKey, TimeSpan.FromSeconds(time)); |
||||
|
if (handle == null) |
||||
|
{ |
||||
|
// 抛出异常 通过监听器使其重试
|
||||
|
throw new AbpBackgroundTaskConcurrentException(context.JobType); |
||||
|
} |
||||
|
|
||||
|
await using (handle) |
||||
|
{ |
||||
|
await InternalExecuteAsync(context); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await InternalExecuteAsync(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task InternalExecuteAsync(JobRunnableContext context) |
||||
|
{ |
||||
|
var jobRunnable = context.ServiceProvider.GetService(context.JobType); |
||||
|
if (jobRunnable == null) |
||||
|
{ |
||||
|
jobRunnable = Activator.CreateInstance(context.JobType); |
||||
|
} |
||||
|
await ((IJobRunnable)jobRunnable).ExecuteAsync(context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
# LINGYUN.Abp.BackgroundTasks |
||||
|
|
||||
|
后台任务(队列)模块,Abp提供的后台作业与后台工作者不支持Cron表达式, 提供可管理的后台任务(队列)功能. |
||||
|
|
||||
|
实现了**Volo.Abp.BackgroundJobs.IBackgroundJobManager**, 意味着您也能通过框架后台作业接口添加新作业. |
||||
|
|
||||
|
## 任务类别 |
||||
|
|
||||
|
* JobType.Once: 一次性任务, 此类型只会被执行一次, 适用于邮件通知等场景 |
||||
|
* JobType.Period: 周期性任务, 此类型任务会根据Cron表达式来决定运行方式, 适用于报表分析等场景 |
||||
|
* JobType.Persistent: 持续性任务, 此类型任务按照给定重复次数、重复间隔运行, 适用于接口压测等场景 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
模块按需引用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpBackgroundTasksModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
```csharp |
||||
|
public class DemoClass |
||||
|
{ |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public DemoClass(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
} |
||||
|
|
||||
|
public async Task Some() |
||||
|
{ |
||||
|
var scheduler = ServiceProvider.GetRequiredService<IJobScheduler>(); |
||||
|
|
||||
|
// 将周期性(5秒一次)任务添加到队列 |
||||
|
await scheduler.QueueAsync(new JobInfo |
||||
|
{ |
||||
|
Type = typeof(ConsoleJob).AssemblyQualifiedName, |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Name = "Test-Console-Period", |
||||
|
Group = "Test", |
||||
|
Description = "Test-Console", |
||||
|
Id = Guid.NewGuid(), |
||||
|
JobType = JobType.Period, |
||||
|
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low, |
||||
|
Cron = "0/5 * * * * ? ", |
||||
|
TryCount = 10, |
||||
|
Status = JobStatus.Running, |
||||
|
// 定义此字段处理并发 |
||||
|
LockTimeOut = 120, |
||||
|
}); |
||||
|
|
||||
|
// 将一次性任务添加到队列, 将在10(Interval)秒后被执行 |
||||
|
await scheduler.QueueAsync(new JobInfo |
||||
|
{ |
||||
|
Type = typeof(ConsoleJob).AssemblyQualifiedName, |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Name = "Test-Console-Once", |
||||
|
Group = "Test", |
||||
|
Description = "Test-Console", |
||||
|
Id = Guid.NewGuid(), |
||||
|
JobType = JobType.Once, |
||||
|
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low, |
||||
|
Interval = 10, |
||||
|
TryCount = 10, |
||||
|
Status = JobStatus.Running, |
||||
|
}); |
||||
|
|
||||
|
// 将持续性任务添加到队列, 将在10(Interval)秒后被执行, 最大执行5(MaxCount)次 |
||||
|
await scheduler.QueueAsync(new JobInfo |
||||
|
{ |
||||
|
Type = typeof(ConsoleJob).AssemblyQualifiedName, |
||||
|
Args = new Dictionary<string, object>(), |
||||
|
Name = "Test-Console-Persistent", |
||||
|
Group = "Test", |
||||
|
Description = "Test-Console", |
||||
|
Id = Guid.NewGuid(), |
||||
|
JobType = JobType.Persistent, |
||||
|
Priority = Volo.Abp.BackgroundJobs.BackgroundJobPriority.Low, |
||||
|
Interval = 10, |
||||
|
TryCount = 10, |
||||
|
MaxCount = 5, |
||||
|
Status = JobStatus.Running, |
||||
|
}); |
||||
|
|
||||
|
// 同样可以把框架后台作业添加到作业调度器中, 不需要更改使用习惯 |
||||
|
var backgroundJobManager = ServiceProvider.GetRequiredService<IBackgroundJobManager>(); |
||||
|
await jobManager.EnqueueAsync( |
||||
|
new SmsJobArgs |
||||
|
{ |
||||
|
PhoneNumber = "13800138000", |
||||
|
Message = "来自框架的后台工作者" |
||||
|
}, |
||||
|
BackgroundJobPriority.High, |
||||
|
TimeSpan.FromSeconds(10)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SmsJobArgs |
||||
|
{ |
||||
|
public string PhoneNumber { get; set; } |
||||
|
public string Message { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class SmsJob : AsyncBackgroundJob<SmsJobArgs>, ITransientDependency |
||||
|
{ |
||||
|
public override Task ExecuteAsync(SmsJobArgs args) |
||||
|
{ |
||||
|
Console.WriteLine($"Send sms message: {args.Message}"); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Authorization" Version="$(VoloAbpPackageVersion)" /> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoCreateDto : BackgroundJobInfoCreateOrUpdateDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxNameLength))] |
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxGroupLength))] |
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxTypeLength))] |
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public DateTime BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Validation; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public abstract class BackgroundJobInfoCreateOrUpdateDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool IsEnabled { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public ExtraPropertyDictionary Args { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxDescriptionLength))] |
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public JobType JobType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是持续任务需要指定
|
||||
|
/// </summary>
|
||||
|
[DynamicStringLength(typeof(BackgroundJobInfoConsts), nameof(BackgroundJobInfoConsts.MaxCronLength))] |
||||
|
public string Cron { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int MaxTryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public int Interval { get; set; } = 300; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority Priority { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public int LockTimeOut { get; set; } |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoDto : ExtensibleAuditedEntityDto<Guid>, IHasConcurrencyStamp |
||||
|
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 返回参数
|
||||
|
/// </summary>
|
||||
|
public string Result { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务参数
|
||||
|
/// </summary>
|
||||
|
public ExtraPropertyDictionary Args { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次运行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? LastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 下一次执行时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? NextRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类别
|
||||
|
/// </summary>
|
||||
|
public JobType JobType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// Cron表达式,如果是持续任务需要指定
|
||||
|
/// </summary>
|
||||
|
public string Cron { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 触发次数
|
||||
|
/// </summary>
|
||||
|
public int TriggerCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试次数
|
||||
|
/// </summary>
|
||||
|
public int TryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 失败重试上限
|
||||
|
/// 默认:50
|
||||
|
/// </summary>
|
||||
|
public int MaxTryCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 最大执行次数
|
||||
|
/// 默认:0, 无限制
|
||||
|
/// </summary>
|
||||
|
public int MaxCount { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 连续失败且不会再次执行
|
||||
|
/// </summary>
|
||||
|
public bool IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否启用
|
||||
|
/// </summary>
|
||||
|
public bool IsEnabled { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 间隔时间,单位秒,与Cron表达式冲突
|
||||
|
/// 默认: 300
|
||||
|
/// </summary>
|
||||
|
public int Interval { get; set; } = 300; |
||||
|
/// <summary>
|
||||
|
/// 任务优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority Priority { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务独占超时时长(秒)
|
||||
|
/// 0或更小不生效
|
||||
|
/// </summary>
|
||||
|
public int LockTimeOut { get; set; } |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoGetListInput: PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 其他过滤条件
|
||||
|
/// </summary>
|
||||
|
public string Filter { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务状态
|
||||
|
/// </summary>
|
||||
|
public JobStatus? Status { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次起始触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginLastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 上次截止触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndLastRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 起始创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginCreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 截止创建时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndCreationTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 是否已放弃任务
|
||||
|
/// </summary>
|
||||
|
public bool? IsAbandoned { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public JobType? JobType { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 优先级
|
||||
|
/// </summary>
|
||||
|
public JobPriority? Priority { get; set; } |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobInfoUpdateDto : BackgroundJobInfoCreateOrUpdateDto, IHasConcurrencyStamp |
||||
|
{ |
||||
|
public string ConcurrencyStamp { get; set; } |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobLogDto : EntityDto<long> |
||||
|
{ |
||||
|
public string JobName { get; set; } |
||||
|
public string JobGroup { get; set; } |
||||
|
public string JobType { get; set; } |
||||
|
public string Message { get; set; } |
||||
|
public DateTime RunTime { get; set; } |
||||
|
public string Exception { get; set; } |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class BackgroundJobLogGetListInput : PagedAndSortedResultRequestDto |
||||
|
{ |
||||
|
public Guid? JobId { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 其他过滤条件
|
||||
|
/// </summary>
|
||||
|
public string Filter { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 存在异常
|
||||
|
/// </summary>
|
||||
|
public bool? HasExceptions { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务名称
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务分组
|
||||
|
/// </summary>
|
||||
|
public string Group { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 任务类型
|
||||
|
/// </summary>
|
||||
|
public string Type { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 开始触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? BeginRunTime { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 结束触发时间
|
||||
|
/// </summary>
|
||||
|
public DateTime? EndRunTime { get; set; } |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public interface IBackgroundJobInfoAppService : |
||||
|
ICrudAppService< |
||||
|
BackgroundJobInfoDto, |
||||
|
Guid, |
||||
|
BackgroundJobInfoGetListInput, |
||||
|
BackgroundJobInfoCreateDto, |
||||
|
BackgroundJobInfoUpdateDto> |
||||
|
{ |
||||
|
Task TriggerAsync(Guid id); |
||||
|
|
||||
|
Task PauseAsync(Guid id); |
||||
|
|
||||
|
Task ResumeAsync(Guid id); |
||||
|
|
||||
|
Task StopAsync(Guid id); |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public interface IBackgroundJobLogAppService : |
||||
|
IReadOnlyAppService< |
||||
|
BackgroundJobLogDto, |
||||
|
long, |
||||
|
BackgroundJobLogGetListInput>, |
||||
|
IDeleteAppService<long> |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using LINGYUN.Abp.TaskManagement.Localization; |
||||
|
using Volo.Abp.Authorization.Permissions; |
||||
|
using Volo.Abp.Localization; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement.Permissions; |
||||
|
|
||||
|
public class TaskManagementPermissionDefinitionProvider : PermissionDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(IPermissionDefinitionContext context) |
||||
|
{ |
||||
|
var group = context.AddGroup( |
||||
|
TaskManagementPermissions.GroupName, |
||||
|
L("Permissions:TaskManagement"), |
||||
|
MultiTenancySides.Host); |
||||
|
|
||||
|
var backgroundJobs = group.AddPermission( |
||||
|
TaskManagementPermissions.BackgroundJobs.Default, |
||||
|
L("Permissions:BackgroundJobs"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Create, |
||||
|
L("Permissions:CreateJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Update, |
||||
|
L("Permissions:UpdateJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Delete, |
||||
|
L("Permissions:DeleteJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Trigger, |
||||
|
L("Permissions:TriggerJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Pause, |
||||
|
L("Permissions:PauseJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Resume, |
||||
|
L("Permissions:ResumeJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobs.Stop, |
||||
|
L("Permissions:StopJob"), |
||||
|
MultiTenancySides.Host); |
||||
|
|
||||
|
var backgroundJobLogs = group.AddPermission( |
||||
|
TaskManagementPermissions.BackgroundJobLogs.Default, |
||||
|
L("Permissions:BackgroundJobLogs"), |
||||
|
MultiTenancySides.Host); |
||||
|
backgroundJobLogs.AddChild( |
||||
|
TaskManagementPermissions.BackgroundJobLogs.Delete, |
||||
|
L("Permissions:DeleteJobLogs"), |
||||
|
MultiTenancySides.Host); |
||||
|
} |
||||
|
|
||||
|
private ILocalizableString L(string name) |
||||
|
{ |
||||
|
return LocalizableString.Create<TaskManagementResource>(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement.Permissions; |
||||
|
|
||||
|
public static class TaskManagementPermissions |
||||
|
{ |
||||
|
public const string GroupName = "TaskManagement"; |
||||
|
|
||||
|
public static class BackgroundJobs |
||||
|
{ |
||||
|
public const string Default = GroupName + ".BackgroundJobs"; |
||||
|
public const string Create = Default + ".Create"; |
||||
|
public const string Update = Default + ".Update"; |
||||
|
public const string Delete = Default + ".Delete"; |
||||
|
public const string Trigger = Default + ".Trigger"; |
||||
|
public const string Pause = Default + ".Pause"; |
||||
|
public const string Resume = Default + ".Resume"; |
||||
|
public const string Stop = Default + ".Stop"; |
||||
|
} |
||||
|
|
||||
|
public static class BackgroundJobLogs |
||||
|
{ |
||||
|
public const string Default = GroupName + ".BackgroundJobLogs"; |
||||
|
public const string Delete = Default + ".Delete"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Application; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[DependsOn(typeof(TaskManagementDomainSharedModule))] |
||||
|
[DependsOn(typeof(AbpDddApplicationContractsModule))] |
||||
|
public class TaskManagementApplicationContractsModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public static class TaskManagementRemoteServiceConsts |
||||
|
{ |
||||
|
public const string RemoteServiceName = "TaskManagement"; |
||||
|
|
||||
|
public const string ModuleName = "task-management"; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
||||
|
<xs:element name="Weavers"> |
||||
|
<xs:complexType> |
||||
|
<xs:all> |
||||
|
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
||||
|
<xs:complexType> |
||||
|
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:all> |
||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
||||
|
<xs:annotation> |
||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
||||
|
</xs:annotation> |
||||
|
</xs:attribute> |
||||
|
</xs:complexType> |
||||
|
</xs:element> |
||||
|
</xs:schema> |
||||
@ -0,0 +1,20 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Application.Contracts\LINGYUN.Abp.TaskManagement.Application.Contracts.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.TaskManagement.Domain\LINGYUN.Abp.TaskManagement.Domain.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,176 @@ |
|||||
|
using LINGYUN.Abp.BackgroundTasks; |
||||
|
using LINGYUN.Abp.TaskManagement.Permissions; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
using Volo.Abp.Data; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Default)] |
||||
|
public class BackgroundJobInfoAppService : TaskManagementApplicationService, IBackgroundJobInfoAppService |
||||
|
{ |
||||
|
protected BackgroundJobManager BackgroundJobManager { get; } |
||||
|
protected IBackgroundJobInfoRepository BackgroundJobInfoRepository { get; } |
||||
|
|
||||
|
public BackgroundJobInfoAppService( |
||||
|
BackgroundJobManager backgroundJobManager, |
||||
|
IBackgroundJobInfoRepository backgroundJobInfoRepository) |
||||
|
{ |
||||
|
BackgroundJobManager = backgroundJobManager; |
||||
|
BackgroundJobInfoRepository = backgroundJobInfoRepository; |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Create)] |
||||
|
public virtual async Task<BackgroundJobInfoDto> CreateAsync(BackgroundJobInfoCreateDto input) |
||||
|
{ |
||||
|
if (await BackgroundJobInfoRepository.CheckNameAsync(input.Group, input.Name)) |
||||
|
{ |
||||
|
throw new BusinessException(TaskManagementErrorCodes.JobNameAlreadyExists) |
||||
|
.WithData("Group", input.Group) |
||||
|
.WithData("Name", input.Name); |
||||
|
} |
||||
|
|
||||
|
var backgroundJobInfo = new BackgroundJobInfo( |
||||
|
GuidGenerator.Create(), |
||||
|
input.Name, |
||||
|
input.Group, |
||||
|
input.Type, |
||||
|
input.Args, |
||||
|
input.BeginTime, |
||||
|
input.EndTime, |
||||
|
input.Priority, |
||||
|
input.MaxCount, |
||||
|
input.MaxTryCount); |
||||
|
|
||||
|
UpdateByInput(backgroundJobInfo, input); |
||||
|
|
||||
|
await BackgroundJobInfoRepository.InsertAsync(backgroundJobInfo, autoSave: true); |
||||
|
|
||||
|
if (backgroundJobInfo.IsEnabled && backgroundJobInfo.JobType == JobType.Period) |
||||
|
{ |
||||
|
await BackgroundJobManager.QueueAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Delete)] |
||||
|
public virtual async Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.DeleteAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<BackgroundJobInfoDto> GetAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<PagedResultDto<BackgroundJobInfoDto>> GetListAsync(BackgroundJobInfoGetListInput input) |
||||
|
{ |
||||
|
var filter = new BackgroundJobInfoFilter |
||||
|
{ |
||||
|
IsAbandoned = input.IsAbandoned, |
||||
|
JobType = input.JobType, |
||||
|
BeginCreationTime = input.BeginCreationTime, |
||||
|
EndCreationTime = input.EndCreationTime, |
||||
|
BeginLastRunTime = input.BeginLastRunTime, |
||||
|
EndLastRunTime = input.EndLastRunTime, |
||||
|
BeginTime = input.BeginTime, |
||||
|
EndTime = input.EndTime, |
||||
|
Filter = input.Filter, |
||||
|
Group = input.Group, |
||||
|
Name = input.Name, |
||||
|
Priority = input.Priority, |
||||
|
Status = input.Status, |
||||
|
Type = input.Type |
||||
|
}; |
||||
|
var totalCount = await BackgroundJobInfoRepository.GetCountAsync(filter); |
||||
|
var backgroundJobInfos = await BackgroundJobInfoRepository.GetListAsync( |
||||
|
filter, input.Sorting, input.MaxResultCount, input.SkipCount); |
||||
|
|
||||
|
return new PagedResultDto<BackgroundJobInfoDto>(totalCount, |
||||
|
ObjectMapper.Map<List<BackgroundJobInfo>, List<BackgroundJobInfoDto>>(backgroundJobInfos)); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Pause)] |
||||
|
public virtual async Task PauseAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.PauseAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Resume)] |
||||
|
public virtual async Task ResumeAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.ResumeAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Trigger)] |
||||
|
public virtual async Task TriggerAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.TriggerAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Stop)] |
||||
|
public virtual async Task StopAsync(Guid id) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
await BackgroundJobManager.StopAsync(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobs.Update)] |
||||
|
public virtual async Task<BackgroundJobInfoDto> UpdateAsync(Guid id, BackgroundJobInfoUpdateDto input) |
||||
|
{ |
||||
|
var backgroundJobInfo = await BackgroundJobInfoRepository.GetAsync(id); |
||||
|
|
||||
|
var resetJob = backgroundJobInfo.JobType == input.JobType; |
||||
|
|
||||
|
UpdateByInput(backgroundJobInfo, input); |
||||
|
|
||||
|
backgroundJobInfo.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); |
||||
|
|
||||
|
await BackgroundJobManager.UpdateAsync(backgroundJobInfo, resetJob); |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobInfo, BackgroundJobInfoDto>(backgroundJobInfo); |
||||
|
} |
||||
|
|
||||
|
protected virtual void UpdateByInput(BackgroundJobInfo backgroundJobInfo, BackgroundJobInfoCreateOrUpdateDto input) |
||||
|
{ |
||||
|
backgroundJobInfo.IsEnabled = input.IsEnabled; |
||||
|
backgroundJobInfo.LockTimeOut = input.LockTimeOut; |
||||
|
backgroundJobInfo.Description = input.Description; |
||||
|
backgroundJobInfo.MaxCount = input.MaxCount; |
||||
|
backgroundJobInfo.MaxTryCount = input.MaxTryCount; |
||||
|
|
||||
|
backgroundJobInfo.Args.RemoveAll(x => !input.Args.ContainsKey(x.Key)); |
||||
|
backgroundJobInfo.Args.AddIfNotContains(input.Args); |
||||
|
|
||||
|
backgroundJobInfo.SetPriority(input.Priority); |
||||
|
switch (input.JobType) |
||||
|
{ |
||||
|
case JobType.Once: |
||||
|
backgroundJobInfo.SetOnceJob(input.Interval); |
||||
|
break; |
||||
|
case JobType.Persistent: |
||||
|
backgroundJobInfo.SetPersistentJob(input.Interval); |
||||
|
break; |
||||
|
case JobType.Period: |
||||
|
backgroundJobInfo.SetPeriodJob(input.Cron); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using LINGYUN.Abp.TaskManagement.Permissions; |
||||
|
using Microsoft.AspNetCore.Authorization; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Dtos; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobLogs.Default)] |
||||
|
public class BackgroundJobLogAppService : TaskManagementApplicationService, IBackgroundJobLogAppService |
||||
|
{ |
||||
|
protected IBackgroundJobLogRepository BackgroundJobLogRepository { get; } |
||||
|
|
||||
|
public BackgroundJobLogAppService( |
||||
|
IBackgroundJobLogRepository backgroundJobLogRepository) |
||||
|
{ |
||||
|
BackgroundJobLogRepository = backgroundJobLogRepository; |
||||
|
} |
||||
|
|
||||
|
[Authorize(TaskManagementPermissions.BackgroundJobLogs.Delete)] |
||||
|
public virtual Task DeleteAsync(long id) |
||||
|
{ |
||||
|
return BackgroundJobLogRepository.DeleteAsync(id); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<BackgroundJobLogDto> GetAsync(long id) |
||||
|
{ |
||||
|
var backgroundJobLog = await BackgroundJobLogRepository.GetAsync(id); |
||||
|
|
||||
|
return ObjectMapper.Map<BackgroundJobLog, BackgroundJobLogDto>(backgroundJobLog); |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<PagedResultDto<BackgroundJobLogDto>> GetListAsync(BackgroundJobLogGetListInput input) |
||||
|
{ |
||||
|
var filter = new BackgroundJobLogFilter |
||||
|
{ |
||||
|
BeginRunTime = input.BeginRunTime, |
||||
|
EndRunTime = input.EndRunTime, |
||||
|
HasExceptions = input.HasExceptions, |
||||
|
Filter = input.Filter, |
||||
|
Group = input.Group, |
||||
|
Name = input.Name, |
||||
|
Type = input.Type |
||||
|
}; |
||||
|
|
||||
|
var totalCount = await BackgroundJobLogRepository.GetCountAsync(filter, input.JobId); |
||||
|
var backgroundJobLogs = await BackgroundJobLogRepository.GetListAsync( |
||||
|
filter, input.JobId, input.Sorting, input.MaxResultCount, input.SkipCount); |
||||
|
|
||||
|
return new PagedResultDto<BackgroundJobLogDto>(totalCount, |
||||
|
ObjectMapper.Map<List<BackgroundJobLog>, List<BackgroundJobLogDto>>(backgroundJobLogs)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using AutoMapper; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public class TaskManagementApplicationMapperProfile : Profile |
||||
|
{ |
||||
|
public TaskManagementApplicationMapperProfile() |
||||
|
{ |
||||
|
CreateMap<BackgroundJobInfo, BackgroundJobInfoDto>(); |
||||
|
CreateMap<BackgroundJobLog, BackgroundJobLogDto>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Application; |
||||
|
using Volo.Abp.AutoMapper; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
[DependsOn(typeof(TaskManagementApplicationContractsModule))] |
||||
|
[DependsOn(typeof(TaskManagementDomainModule))] |
||||
|
[DependsOn(typeof(AbpDddApplicationModule))] |
||||
|
public class TaskManagementApplicationModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddAutoMapperObjectMapper<TaskManagementApplicationModule>(); |
||||
|
|
||||
|
Configure<AbpAutoMapperOptions>(options => |
||||
|
{ |
||||
|
options.AddProfile<TaskManagementApplicationMapperProfile>(validate: true); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using LINGYUN.Abp.TaskManagement.Localization; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace LINGYUN.Abp.TaskManagement; |
||||
|
|
||||
|
public abstract class TaskManagementApplicationService : ApplicationService |
||||
|
{ |
||||
|
protected TaskManagementApplicationService() |
||||
|
{ |
||||
|
LocalizationResource = typeof(TaskManagementResource); |
||||
|
ObjectMapperContext = typeof(TaskManagementApplicationModule); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue