Маршрутизация и версии workflow
После нормализации три заявки имеют одинаковую форму, но должны попасть в разные редакционные очереди. Добавим два вида решения: проверку пригодности полей и выбор направления по теме. Результатом станет таблица маршрутов для P-001, P-002 и P-003, а также понятная граница между изменением workflow и его публикацией.
Мы используем ожидаемые данные прошлого урока, не результаты проведённого запуска. В модели нет действующего webhook и внешней системы задач. Поэтому настройку веток можно разобрать без настоящих отправок. Правила относятся к подготовке карточек: принятие статьи редактором остаётся отдельным действием.
Собственные файлы этого шага доступны в архиве fixtures и контрактов серии, папка lesson-05. Это приложения к объяснению, а не готовый экспорт workflow.
Проверка формы перед маршрутом
Сначала рассмотрим validation_errors. Пустой массив означает, что известные обязательные поля соответствуют нашей строковой форме. Непустой массив означает необходимость уточнения. Для такого входа задаём очередь input_review и сохраняем причину. Выбор тематической очереди происходит только после этой проверки; он не должен маскировать неправильный тип темы.
В n8n узел If разделяет элементы по условию. Для нескольких условий важны AND и OR: первое требует выполнить все проверки, второе — хотя бы одну. Документация If описывает эти сочетания. Если пригодность проверяется непустым идентификатором и непустым заголовком, нужен AND. Иначе наличие идентификатора могло бы пропустить пустой заголовок.
Проверка exists и проверка непустого текста не равнозначны. Поле может существовать со значением "". После trim строка из пробелов тоже становится пустой. Наш нормализатор уже различает эти случаи и записывает ошибки, поэтому ветке достаточно читать согласованный признак. Не повторяйте в разных узлах слегка разные определения пригодности: они быстро начнут расходиться.
Известная тема и запасной выход
Для нескольких направлений подходит Switch. В режиме Rules задаём точное сравнение темы с web и seo, а для отсутствия совпадения выбираем отдельный запасной выход. В текущей документации этот вариант называется Fallback Output: Extra Output; без него значение, не совпавшее с правилами, может быть проигнорировано. Описание Switch также различает первый совпавший выход и отправку во все совпавшие.
| Заявка | Нормальная тема | Выход | Очередь | Причина |
|---|---|---|---|---|
P-001 |
web |
Web | web_editorial |
Известная тема |
P-002 |
seo |
SEO | seo_editorial |
Известная тема |
P-003 |
web3 |
Fallback | manual_review |
unknown_topic |
| Неверная форма | Не рассматриваем | Ветка проверки | input_review |
Ошибки полей |
Запасной выход не является очередью мусора. Для P-003 это единственное место, где редактор увидит идею нового направления. Если позже появится владелец web3-материалов, правило можно расширить. Пока не следует записывать web_editorial только потому, что первые три буквы похожи. Сравнение по подстроке создало бы другое, не согласованное правило.
Назначение очереди и диспетчеризация по выходам — связанные, но разные части схемы. Очередь должна попасть в подготовленную карточку как поле. Сам провод на canvas не сохраняет объяснение решения в данных. В приложении routing-matrix.json показываем ожидаемые метки всех карточек; downstream-операция сможет прочитать их без знания расположения узлов.
На canvas продолжите выход Normalize proposals узлом If, назовите его Fields valid. Задайте Boolean-условие: значение выражения {{ $json.validation_errors.length === 0 }} должно быть true. Выход true соедините со Switch под именем Topic route. Выход false соедините с Edit Fields под именем Input review: сохраните входные поля, добавьте queue: input_review, reason: invalid_shape, status: needs_input. Значение одобрения не меняйте.
У Topic route выберите Rules. Первое правило String сравнивает {{ $json.topic }} с web, второе — с seo. Добавьте Fallback Output: Extra Output и отключите отправку во все совпавшие выходы. Каждый выход соедините со своим Edit Fields, сохраняя входные поля. Web получает queue: web_editorial, SEO — queue: seo_editorial; у обоих reason задаётся как null. Запасной выход получает queue: manual_review и reason: unknown_topic. Все три узла добавляют status: prepared. Это три независимых результата веток; общий набор автоматически не появляется.
Два провода не создают условие
Если соединить один выход с двумя узлами, получится разветвление данных, а не автоматический выбор одного маршрута. Оба соединения могут участвовать в выполнении. Чтобы P-001 не породила две тематические карточки, задаём взаимно исключающие сравнения и режим первого совпадения. Условия должны отвечать содержанию договора, а не визуальному расположению узлов.
Представим ошибку: первое правило ищет web, второе — строку, содержащую букву e. Тема web подходит обоим. При отправке во все совпавшие выходы заявка окажется сразу в двух местах. Исправление состоит в уточнении правила, а не в удалении карточки из одного результата после выполнения. Задача этого курса — один объяснимый тематический исход для одного пригодного предложения.
Фильтрация отличается от маршрутизации. Фильтр может удалить не прошедший условие item из дальнейшего потока. Для редакционного входа такой исход часто неудобен: неизвестная тема должна получить ручную проверку, а не исчезнуть. Если всё же применяете фильтр для отдельной подборки, сохраняйте исходный набор и описывайте, какие идентификаторы исключены и почему.
Черновик и опубликованная версия
В современных документах n8n изменения сохраняются автоматически как черновик. Производственные выполнения используют опубликованную версию, а не последние изменения canvas; действие называется Publish. Старые записи интерфейса показывают переключатель Activate. Важно понимать различие жизненного цикла, а не искать старую кнопку в новой версии. Документация сохранения и публикации описывает текущий порядок.
Для редакционного workflow это даёт конкретный сценарий. В производстве могли бы работать только очереди web и SEO, а автор добавляет web3 в черновике. Само редактирование узла не делает новую ветку рабочей для поступающих событий. И наоборот, ручное рассмотрение черновика не подтверждает поведение уже опубликованного экземпляра. Поэтому обсуждение изменения должно указывать, к какой версии относится правило.
Свойства workflow и внешние подключения также требуют внимания при настоящей работе. Не выводите универсальное обещание безопасности из одного слова «черновик»: настройки workflow могут иметь отдельное поведение, а ручные действия способны обращаться к сервисам. В нашем локальном наборе published остаётся ложным, credentials отсутствуют. Никакая версия не переносилась в рабочий n8n.
Названия редакционных состояний в JSON — собственные метки проекта. Они не являются фактическими идентификаторами версий n8n. Например, routing-v1 помогает сравнить наше правило тем, но не создаёт сохранённую версию в панели. Разделение меток убирает распространённую путаницу между описанным решением и выполненной операцией сервиса.
Сохранение трёх исходов
Файл приложения содержит ожидаемые карточки с полями queue, reason и status. У P-001 и P-002 причина дополнительной проверки отсутствует, у P-003 записано unknown_topic. Число исходных предложений равно сумме тематических и ручных исходов: один web, один SEO и один fallback. Ни одна карточка не становится одобренной.
При ручном запуске правильно настроенной схемы в собственном учебном экземпляре ожидается по одной карточке на каждом тематическом и запасном выходе. Откройте три узла подготовки в JSON-представлении и сопоставьте id, queue, reason. Если входные данные ещё закреплены от другого сценария, сначала выясните их происхождение: изменение fixture не изменяет ранее закреплённый выход автоматически. Проверка здесь относится к настройке веток, не к настоящей редакционной очереди.
Рассмотрите изменение только одного значения: замените тему P-002 на web3. Ожидается две карточки ручной проверки и одна web-карточка; исходный идентификатор P-002 сохранится. Такое рассуждение проверяет правило без настоящей отправки. В следующем уроке подготовим компактную подборку и разберём, как объединять поля, не теряя связь идентификатора с заголовком.