Перейти к содержимому

SDK (Portal.TimerJob.Sdk)

SDK (Portal.TimerJob.Sdk)

C# SDK для разработки Timer Job. Контракт: backend/src/Portal.TimerJob.Sdk/ITimerJob.cs.

Runtime Worker инжектирует ITimerJobApi — см. backend/src/Portal.Application/TimerJobs/TimerJobApiHost.cs.

Интерфейс обработчика

using Portal.TimerJob.Sdk;
public sealed class ContosoHeartbeatTimerJob : ITimerJob
{
public Task<object?> ExecuteAsync(
TimerJobContext context,
ITimerJobApi api,
CancellationToken cancellationToken = default)
{
var message = context.Config.ValueKind == JsonValueKind.Object
&& context.Config.TryGetProperty("message", out var msgEl)
? msgEl.GetString() ?? "heartbeat"
: "heartbeat";
api.Log.Info("Contoso heartbeat:", message,
"job=", context.JobType,
"timerJobId=", context.TimerJobId,
"scheduledAt=", context.ScheduledAt);
return Task.FromResult<object?>(new { ok = true, message });
}
}

В отличие от Event Receiver, Timer Job не получает прямой доступ к спискам и узлам через SDK. Для работы с данными портала ставьте задания в очередь (api.Jobs.EnqueueAsync) или выносите логику во встроенные job-обработчики.

TimerJobContext

ПолеТипОписание
TimerJobIdGuidID экземпляра из timer_jobs
JobTypestringmanifest.id (например contoso.heartbeat)
ConfigJsonElementПараметры экземпляра из админки (timer_jobs.config)
ScheduledAtDateTimeOffsetВремя планового (или ручного) запуска

Payload задания в Worker

При постановке в очередь Worker получает JSON:

{
"source": "timerJob",
"timerJobId": "uuid-экземпляра",
"jobType": "contoso.heartbeat",
"scheduledAt": "2026-07-06T09:30:00.0000000Z",
"manual": false,
"config": {
"message": "hello"
}
}
  • manual: true — ручной запуск из админки (POST /timer-jobs/{id}/run)
  • manual: false — срабатывание cron-планировщика

Timer Job API (ITimerJobApi)

Логирование (api.Log)

api.Log.Info("Старт обработки", context.JobType);
api.Log.Warn("Пропущена запись", itemId);
api.Log.Error("Ошибка синхронизации:", ex.Message);

Сообщения попадают в лог Worker с префиксом [timer-job]. Просмотр:

Окно терминала
docker compose logs -f worker

Очередь заданий (api.Jobs)

Timer Job может поставить другое задание в portal_jobs:

var result = await api.Jobs.EnqueueAsync(
serviceKey: "audit",
jobType: "audit.logEvent",
jobPayload: new
{
action = "timer.heartbeat",
timerJobId = context.TimerJobId,
message = "tick",
},
cancellationToken);
// result: { id = Guid задания, status = "pending" }

В payload автоматически добавляются:

{
"triggeredBy": "timerJob",
"sourceTimerJobId": "uuid-экземпляра"
}

Поля из jobPayload объединяются с этими служебными полями.

Типичный паттерн: Timer Job по расписанию ставит пакетное задание (например, через Event Receiver job type или встроенный handler), а тяжёлая работа выполняется асинхронно в очереди.

Асинхронное выполнение и отмена

public async Task<object?> ExecuteAsync(
TimerJobContext context,
ITimerJobApi api,
CancellationToken cancellationToken)
{
for (var i = 0; i < 100; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await ProcessBatchAsync(i, cancellationToken);
}
return new { processed = 100 };
}

При остановке Worker активные задания получают CancellationToken от хоста.

Возвращаемое значение

ExecuteAsync возвращает object? — результат сохраняется в portal_jobs (поле результата задания). Используйте для диагностики:

return new { ok = true, processed = count, skipped = skipped };

При необработанном исключении задание переходит в failed (до 3 попыток с интервалом 30 с). См. Job-модули.

Обработка ошибок

public async Task<object?> ExecuteAsync(TimerJobContext context, ITimerJobApi api, CancellationToken ct)
{
try
{
await DoWorkAsync(context, ct);
return new { ok = true };
}
catch (Exception ex)
{
api.Log.Error("Timer job failed:", ex.Message);
throw; // задание будет повторено / помечено failed
}
}

Для «мягких» ошибок (пропуск итерации без fail) верните объект с ok: false и не бросайте исключение.

Шаблон проекта

Окно терминала
cd backend
dotnet new install ./templates/portal-timerjob
dotnet new portal-timerjob -n MyTimerJob -o ../../packages/my-timerjob

Пример: packages/timer-job-examples/heartbeat.

Ограничения SDK

ДоступноНедоступно напрямую
api.Logapi.Lists (как у Event Receiver)
api.Jobs.EnqueueAsyncapi.Nodes
Чтение context.ConfigHTTP-запросы к внешним API (делайте сами в C#)

Если нужен прямой доступ к спискам по расписанию — рассмотрите цепочку: Timer Job → EnqueueAsync → Event Receiver / custom job handler с IReceiverApi.