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

Runtime Event Receivers

Как платформа диспетчеризует события, привязывает обработчики и отлаживает задания. Разработка пакета: обзор, сценарии.

Event Receiver связывает событие домена с фоновым заданием.

При изменении элемента списка API (.NET) вызывает EventDispatchService, который:

  1. Ищет включённые записи в event_receivers для события (listItem.updated и т.д.)
  2. Фильтрует по config.listId, если он задан
  3. Для каждого подписчика создаёт задание в portal_jobs
  4. Worker выполняет обработчик (job_type)

Аналог WebPart для фоновых обработчиков:

  1. Разработайте пакет — см. обзор
  2. Установите .portalevent в Админка → Event Receivers или включите в функциональный модуль .portalmod
  3. Привяжите обработчик к событию (список → Обработчики или админка)

Worker автоматически загружает DLL из объектного хранилища (SeaweedFS S3) и регистрирует job_type = manifest.id. При установке или удалении пакета API отправляет сигнал через Valkey — Worker перезагружает обработчики без docker compose restart worker. Подробнее: Установка расширений без перезапуска. Обработчик получает IReceiverApi для работы со списками (в т.ч. QueryItemsAsync с фильтром по internal_name), узлами и очередью заданий.

public async Task<object?> HandleAsync(EventContext context, IReceiverApi api, CancellationToken ct)
{
var listId = context.Details.GetProperty("listId").GetString();
if (context.ReceiverConfig.TryGetProperty("listId", out var cfg) &&
cfg.GetString() != listId)
return new { skipped = true };
var itemId = context.Details.GetProperty("item").GetProperty("id").GetString();
var item = await api.Lists.GetItemAsync(listId!, itemId!, ct);
api.Log.Info("Updated:", itemId);
return new { ok = true };
}
СобытиеКогдаPayload
listItem.createdпосле создания элемента{ listId, item, user }
listItem.updatedпосле изменения{ listId, item, user }
listItem.deletedпосле удаления{ listId, itemId, user }

Обработчик только для конкретного списка

Заголовок раздела «Обработчик только для конкретного списка»
  1. Откройте список в портале (нужны права edit на список и роль portal_admin)
  2. Вкладка Обработчики
  3. Выберите событие, название и тип задания → Добавить

listId записывается в config автоматически. eventBridge не создаёт задания для других списков.

POST /api/v1/event-receivers
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"eventName": "listItem.updated",
"title": "Заявки: при изменении",
"serviceKey": "audit",
"jobType": "listItem.example",
"config": {
"listId": "uuid-списка"
},
"isEnabled": true
}

Список обработчиков списка:

GET /api/v1/event-receivers?listId=uuid-списка

Рекомендуется: пакет .portalevent — см. обзор.

Альтернатива: встроенный обработчик в Worker (.NET):

  1. Реализуйте IJobHandler и зарегистрируйте в Portal.Worker (см. BuiltinServices / JobProcessor).
  2. Убедитесь, что служба включена: PATCH /api/v1/services/audit{ "isEnabled": true }
  3. Worker запущен: docker compose up -d worker
  4. Создайте Event Receiver с нужным jobType и config.listId

Runtime API для пакетов: backend/src/Portal.Application/EventReceivers/EventReceiverApiHost.cs.

Встроенные обработчики и типы заданий — в Portal.Application/Services/BuiltinServices.cs и каталоге Event Receiver CLI.

{
"eventName": "listItem.updated",
"user": { "id": "...", "login": "..." },
"resourceType": null,
"resourceId": null,
"details": {
"listId": "uuid",
"item": { "id": "...", "field_values": {} },
"user": {}
},
"receiverConfig": {
"listId": "uuid"
}
}
СобытиеЗадание по умолчанию
page.created / updated / deletedaudit.logEvent
file.uploadedaudit.logEvent

PATCH /api/v1/event-receivers/{id} с { "isEnabled": false } — задания не создаются.

МестоОписание
Страница списка → ОбработчикиReceivers с config.listId для этого списка
Админка → Event ReceiversВсе подписчики портала

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

  1. Админка → Задания — статус pending / completed / failed
  2. audit_logs — если подключён audit.logEvent
  3. Логи Worker: docker compose logs worker (или консоль локального Portal.Worker)