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

Runtime Timer Jobs

Планировщик, API экземпляров и отладка. Разработка пакета: обзор.

Установка и обновление пакетов .portaltimer не требует перезапуска Worker — см. Установка расширений без перезапуска.

Timer Job выполняет C#-код по расписанию, без привязки к событиям домена. Типичные сценарии:

  • ночная очистка / архивация;
  • периодическая синхронизация с внешней системой;
  • отправка дайджестов;
  • постановка пакетных заданий в очередь portal_jobs.

Пакет .portaltimer можно включить в функциональный модуль .portalmod вместе с WebPart и инфраструктурой портала.

Для реакции на изменения списков используйте Event Receivers.

1. Админ устанавливает .portaltimer
→ timer_job_packages + timer_job_definitions
→ Worker перезагружает DLL (Valkey signal)
2. Админ создаёт экземпляр timer_jobs
→ scheduleType + scheduleConfig → cron_expression
→ config (параметры из configSchema)
3. CronSchedulerWorker (каждые ~60 с)
→ CronSchedulerService.TickTimerJobsAsync
→ если next cron <= now → portal_jobs (pending)
→ обновляет last_run_at
4. JobQueueWorker (каждые ~5 с)
→ JobProcessor → TimerJobPackageLoader.InvokeAsync
→ ITimerJob.ExecuteAsync
→ portal_jobs: completed / failed

Ручной запуск (POST /timer-jobs/{id}/run) пропускает шаг 3 и сразу ставит задание в очередь с manual: true.

КомпонентРасположениеРоль
CronSchedulerServicePortal.Application/Jobs/По расписанию ставит jobs в очередь
TimerJobServicePortal.Application/TimerJobs/CRUD экземпляров, RunNow
TimerJobPackageLoaderPortal.Application/TimerJobs/Загрузка DLL из S3, вызов ITimerJob
TimerJobCatalogServicePortal.Application/TimerJobs/Установка/удаление пакетов
JobProcessorPortal.Application/Jobs/Маршрутизация job_type → handler
JobQueueWorkerPortal.WorkerОпрос очереди
CronSchedulerWorkerPortal.WorkerТик планировщика

Переменные Worker:

ПеременнаяПо умолчаниюОписание
WORKER_POLL_INTERVAL_MS5000Интервал опроса очереди
WORKER_BATCH_SIZE10Заданий за один тик
CRON_RELOAD_INTERVAL_MS60000Интервал тика планировщика расписания

Админка и API принимают scheduleType + scheduleConfig (понятные типы: ежедневно, еженедельно и т.д.). Платформа вычисляет cronExpression (стандартный формат: мин час день месяц день_недели).

scheduleTypescheduleConfigCron (внутри)Пример
everyMinute{}* * * * *Каждую минуту
everyNMinutes{ "intervalMinutes": 5 }*/5 * * * *Каждые 5 мин (1–59)
hourly{ "minute": 30 }30 * * * *Каждый час в :30
daily{ "time": "09:30" }30 9 * * *Ежедневно в 09:30
weekly{ "daysOfWeek": [1,3,5], "time": "09:00" }0 9 * * 1,3,5Пн, ср, пт в 09:00
monthly{ "dayOfMonth": 15, "time": "09:00" }0 9 15 * *15-го числа
yearly{ "month": 3, "day": 15, "time": "12:00" }0 12 15 3 *15 марта в 12:00

Дни недели (daysOfWeek): 0 = воскресенье, 1 = понедельник, … 6 = суббота.

Время (time): строка HH:mm, локальный часовой пояс Worker.

Список типов: GET /api/v1/timer-jobs/schedule-types.

Каждую минуту:

{
"title": "Heartbeat",
"definitionId": "uuid-обработчика",
"scheduleType": "everyMinute",
"scheduleConfig": {},
"config": { "message": "tick" },
"isEnabled": true
}

Еженедельно по будням в 08:00:

{
"scheduleType": "weekly",
"scheduleConfig": {
"daysOfWeek": [1, 2, 3, 4, 5],
"time": "08:00"
}
}

Базовый путь: /api/v1/timer-jobs. Требуется авторизация; операции с пакетами и экземплярами — только admin.

МетодПутьОписание
GET/packagesУстановленные пакеты
POST/packagesmultipart package — файл .portaltimer (≤ 5 МБ)
DELETE/packages/{id}Удалить пакет, определения и связанные экземпляры
МетодПутьОписание
GET/definitionsСписок (?admin=true — включая выключенные)
PATCH/definitions/{id}{ "isEnabled": true/false }
GET/job-typesТипы для постановки jobs (из пакетов)
МетодПутьОписание
GET/Все экземпляры
GET/{id}Один экземпляр
POST/Создать (см. CreateTimerJobRequest)
PATCH/{id}Обновить title, schedule, config, isEnabled
DELETE/{id}Удалить
POST/{id}/runРучной запуск → { enqueued, jobId }
МетодПутьОписание
GET/schedule-typesТипы расписания для UI
{
"title": "Ночная синхронизация",
"definitionId": "uuid-из-timer_job_definitions",
"scheduleType": "daily",
"scheduleConfig": { "time": "02:00" },
"config": {
"message": "nightly run",
"dryRun": false
},
"isEnabled": true
}

Ответ включает вычисленные поля:

{
"id": "...",
"cronExpression": "0 2 * * *",
"scheduleDescription": "Ежедневно в 02:00",
"lastRunAt": null,
"jobType": "contoso.heartbeat",
"serviceKey": "timer-extensions"
}

Вкладка Timer Jobs (adminTimerJobsPanel.js):

  1. Пакеты — загрузка .portaltimer, список установленных, удаление
  2. Каталог обработчиков — включение/выключение definition
  3. Задания по расписанию — создание, вкл/выкл, ручной Запуск, удаление

Форма создания задания:

  • выбор обработчика из каталога;
  • тип расписания + параметры (время, дни недели, интервал);
  • поля config по configSchema из manifest.

Если обработчик обращается к спискам через api.Lists, в config должен быть runAsUserId — UUID пользователя, от имени которого выполняются проверки прав (см. SDK Timer Jobs).

При установке первого пакета создаётся служба timer-extensions в portal_services. Она должна быть включена (по умолчанию — да).

Проверка: Админка → Службы или GET /api/v1/services.

Брейкпоинты в IDE (Debug-пакет с PDB + Attach к Worker): Отладка расширений.

  • Экземпляр isEnabled: true?
  • Definition isEnabled: true?
  • Worker запущен? docker compose ps worker
  • Корректное расписание? Смотрите scheduleDescription / cronExpression в ответе API
  • lastRunAt обновляется? Если нет — смотрите логи CronSchedulerWorker
  • Админка → Заданияerror_message
  • Логи Worker: docker compose logs worker
  • Ошибка загрузки DLL: TimerJobPackageLoader пишет в лог при старте Worker
  • Переустановите пакет с увеличенной version
Окно терминала
# Установка + создание + run (полный цикл)
./scripts/e2e-timer-jobs-test.sh
api.Log.Info("debug:", someValue);

В логах Worker: [timer-job] debug: ...

Timer Job может инициировать другие задания:

await api.Jobs.EnqueueAsync("audit", "audit.logEvent", new { action = "nightly" });

Полезно, когда по расписанию нужно запустить тяжёлую или длительную работу отдельным job type (в т.ч. Event Receiver из пакета .portalevent).

См. Job-модули — статусы, повторы, POST /jobs/{id}/retry.

В Portal есть два механизма расписания:

Timer Jobs (.portaltimer)Службы (portal_services)
КонфигурацияАдминка → Timer JobsАдминка → Службы → Расписание
КодC# DLL в пакетеВстроенные handlers Worker
Примерыcontoso.heartbeatad-sync, search-crawler
API в handlerLog + Enqueue jobsПолный доступ службы

Встроенные службы с расписанием описаны в Службы.