Timer Jobs
Timer Jobs
Задания по расписанию (аналог SharePoint Timer Jobs). Разработка пакетов — см. Timer Jobs Portal.
Назначение
Timer Job выполняет C#-код по расписанию (cron), без привязки к событиям домена. Типичные сценарии:
- ночная очистка / архивация;
- периодическая синхронизация с внешней системой;
- отправка дайджестов;
- постановка пакетных заданий в очередь
portal_jobs.
Для реакции на изменения списков используйте 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.
Компоненты
| Компонент | Расположение | Роль |
|---|---|---|
CronSchedulerService | Portal.Application/Jobs/ | Преобразует cron → постановка jobs |
TimerJobService | Portal.Application/TimerJobs/ | CRUD экземпляров, RunNow |
TimerJobPackageLoader | Portal.Application/TimerJobs/ | Загрузка DLL из S3, вызов ITimerJob |
TimerJobCatalogService | Portal.Application/TimerJobs/ | Установка/удаление пакетов |
JobProcessor | Portal.Application/Jobs/ | Маршрутизация job_type → handler |
JobQueueWorker | Portal.Worker | Опрос очереди |
CronSchedulerWorker | Portal.Worker | Тик планировщика |
Переменные Worker:
| Переменная | По умолчанию | Описание |
|---|---|---|
WORKER_POLL_INTERVAL_MS | 5000 | Интервал опроса очереди |
WORKER_BATCH_SIZE | 10 | Заданий за один тик |
CRON_RELOAD_INTERVAL_MS | 60000 | Интервал тика cron-планировщика |
Типы расписания
Админка и API принимают scheduleType + scheduleConfig. Платформа вычисляет cron (стандартный формат: мин час день месяц день_недели).
| scheduleType | scheduleConfig | Cron | Пример |
|---|---|---|---|
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 (admin)
Базовый путь: /api/v1/timer-jobs. Требуется авторизация; операции с пакетами и экземплярами — только admin.
Пакеты
| Метод | Путь | Описание |
|---|---|---|
| GET | /packages | Установленные пакеты |
| POST | /packages | multipart 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):
- Пакеты — загрузка
.portaltimer, список установленных, удаление - Каталог обработчиков — включение/выключение definition
- Задания по расписанию — создание, вкл/выкл, ручной Запуск, удаление
Форма создания задания:
- выбор обработчика из каталога;
- тип расписания + параметры (время, дни недели, интервал);
- поля
configпоconfigSchemaиз manifest.
Служба timer-extensions
При установке первого пакета создаётся служба timer-extensions в portal_services. Она должна быть включена (по умолчанию — да).
Проверка: Админка → Службы или GET /api/v1/services.
Отладка
1. Задание не запускается по расписанию
- Экземпляр
isEnabled: true? - Definition
isEnabled: true? - Worker запущен?
docker compose ps worker - Корректный cron? Смотрите
cronExpressionв ответе API lastRunAtобновляется? Если нет — смотрите логиCronSchedulerWorker
2. Задание в очереди, но failed
- Админка → Задания —
error_message - Логи Worker:
docker compose logs worker - Ошибка загрузки DLL:
TimerJobPackageLoaderпишет в лог при старте Worker - Переустановите пакет с увеличенной
version
3. Ручная проверка
# Установка + создание + run (полный цикл)./scripts/e2e-timer-jobs-test.sh4. Логи обработчика
api.Log.Info("debug:", someValue);В логах Worker: [timer-job] debug: ...
Цепочка с очередью jobs
Timer Job может инициировать другие задания:
await api.Jobs.EnqueueAsync("audit", "audit.logEvent", new { action = "nightly" });Полезно, когда по расписанию нужно запустить тяжёлую или длительную работу отдельным job type (в т.ч. Event Receiver из пакета .portalevent).
См. Job-модули — статусы, повторы, POST /jobs/{id}/retry.
Сравнение с cron служб
В Portal есть два механизма cron:
| Timer Jobs (.portaltimer) | Службы (portal_services) | |
|---|---|---|
| Конфигурация | Админка → Timer Jobs | Админка → Службы |
| Код | C# DLL в пакете | Встроенные handlers Worker |
| Примеры | contoso.heartbeat | ad-sync, search-index |
| API в handler | Log + Enqueue jobs | Полный доступ службы |
Встроенные cron-службы описаны в Service Framework.