Три файла в n8n не обязательно означают три items. Несколько файлов могут лежать в разных binary-полях одного item — именно такой пакет удобно упаковать в один ZIP. Соберём его из учебных CSV, TXT и JSON, распакуем и сравним содержимое с оригиналом байт в байт.
Результат инструкции
Получим ezgpt-demo.zip с orders.csv, readme.txt и metadata.json. Первый Compression создаст архив, второй распакует его. Финальная проверка вернёт один JSON с file_count 3 и roundtrip_ok true, только если сохранились все имена и все байты. AI-модель и внешние сервисы для этого не нужны.
Работаем только с вымышленными данными. Схема не отправляет архив и не записывает его в ваши рабочие папки. n8n хранит binary-данные в своём хранилище выполнений; при файловом режиме оно находится на диске сервера n8n. Для скачивания готового ZIP нужно открыть выход Make ZIP, а не искать архив в итоговом JSON.
Что понадобится
- Работающий n8n и права создать отдельный учебный workflow. Проверено на n8n 2.42.4, Compression версии 1.1.
- Доступ к Code на JavaScript. Учебные файлы создаются прямо в схеме, без чтения папок компьютера и без API-ключей.
- Отдельное тестовое выполнение. Сюда не нужно загружать договоры, клиентские базы и неизвестные архивы из почты.
Установить n8n локально через Docker
Шаг 1. Импортируйте готовый workflow
Скачать n8n-zip-roundtrip.json
Создайте новый workflow. В меню редактора выберите Import from File и загрузите JSON. После импорта должно быть семь узлов:
Manual Trigger → Sample Files → Check Files → Make ZIP
→ Check ZIP → Open ZIP → Check Roundtrip
Make ZIP и Open ZIP — штатный Compression. Sample Files, Check Files, Check ZIP и Check Roundtrip — Code. Схема неактивна, credentials и ID чужого workflow в файле нет. При ручной сборке сохраните эти имена: последний Code получает эталон из Check Files.
Шаг 2. Разделите понятия поля и файла
В примере один item содержит три binary-поля: orders, readme и meta. Внутри них лежат файлы orders.csv, readme.txt и metadata.json. В настройку Input Binary Field(s) вводятся имена полей через запятую, а не имена файлов и не путь к папке.
Например, orders — имя binary-поля, orders.csv — значение его metadata.fileName, а 001 — ID заказа внутри CSV. Эти три имени решают разные задачи. Файлы не должны называться одинаково: при упаковке одинаковые имена могут привести к потере одного из содержимых, поэтому проверим их заранее.
Скачать пример ZIP, созданный проверенным workflow
Готовый архив нужен только как образец. Для выполнения инструкции он не требуется: ваши три файла создаст следующий Code. Образец содержит только вымышленные записи и отдельно проверен после создания.
Шаг 3. Создайте три учебных файла
В Sample Files выберите Mode: Run Once for All Items, Language: JavaScript. Вставьте весь код:
const files = [
{
"field": "orders",
"name": "orders.csv",
"mime": "text/csv",
"text": "order_id,client,amount_minor\n001,Альфа,150000\n002,Бета,0\n"
},
{
"field": "readme",
"name": "readme.txt",
"mime": "text/plain",
"text": "Учебный архив ezGPT. Только вымышленные данные.\n"
},
{
"field": "meta",
"name": "metadata.json",
"mime": "application/json",
"text": "{\"batch_id\":\"zip-demo-001\",\"currency\":\"RUB\"}\n"
}
];
const binary = {};
for (const file of files) {
binary[file.field] = await this.helpers.prepareBinaryData(
Buffer.from(file.text, 'utf8'), file.name, file.mime,
);
}
return [{ json: { batch_id: 'zip-demo-001' }, binary }];
Выполните workflow от Manual Trigger. В Output Sample Files ожидается один item. Во вкладке Binary должны быть orders, readme и meta, а не JSON-строки с такими именами. prepareBinaryData создаёт файловые данные и метаданные; Buffer.from кодирует исходный текст в UTF-8.
orders.csv содержит два вымышленных заказа. Его текст:
order_id,client,amount_minor
001,Альфа,150000
002,Бета,0
Ведущие нули и нулевая сумма здесь сохраняются как текст файла. Мы пока не разбираем CSV в числа и не проверяем бухгалтерский смысл строк. Если открыть CSV в табличном редакторе, он может сам преобразовать типы: это уже отдельное действие, не свойство ZIP.
Шаг 4. Проверьте входные файлы
В Check Files задайте Run Once for All Items, JavaScript и вставьте:
const items = $input.all();
if (items.length !== 1) throw new Error('one_file_item_required');
const item = items[0];
const binary = item.binary || {};
if (Object.keys(binary).sort().join('|') !== 'meta|orders|readme') {
throw new Error('binary_fields_mismatch');
}
const names = new Set();
const manifest = [];
const expected = { orders: 'orders.csv', readme: 'readme.txt', meta: 'metadata.json' };
for (const [field, fileName] of Object.entries(expected)) {
const actualName = binary[field].fileName;
if (typeof actualName !== 'string' || !actualName || names.has(actualName)) {
throw new Error('file_name_missing_or_duplicate');
}
if (actualName !== fileName) throw new Error('file_name_mismatch');
names.add(actualName);
const buffer = await this.helpers.getBinaryDataBuffer(0, field);
if (!buffer.length || buffer.length > 16384) throw new Error('file_size_invalid');
const text = buffer.toString('utf8');
if (!Buffer.from(text, 'utf8').equals(buffer)) throw new Error('file_utf8_invalid');
manifest.push({ file_name: fileName, bytes: buffer.length, text });
}
return [{ json: { batch_id: item.json.batch_id, manifest }, binary }];
Разрешён ровно один item с тремя нужными binary-полями. Каждое имя файла должно совпадать с ожидаемым, содержимое не должно быть пустым или больше 16384 байт, текст обязан корректно восстанавливаться из UTF-8. Значение 16384 — учебное ограничение отдельного файла, не предел возможностей Compression.
Для чтения файла используется this.helpers.getBinaryDataBuffer, а не поле binary.orders.data. В файловом режиме data может быть служебной ссылкой, а не base64 с содержимым. В JSON сохраняется manifest с именами, длиной и исходным текстом: он нужен для проверки после распаковки. Для больших или конфиденциальных файлов такой текстовый эталон нужно заменить другим подходом и продумать срок хранения выполнений.
В Settings всех узлов оставьте On Error: Stop Workflow. Always Output Data, Continue On Fail, Retry On Fail и Execute Once не включайте. Ошибка имени или пропущенный файл не исправятся повторной упаковкой.
Шаг 5. Создайте ZIP
В Make ZIP выберите Operation: Compress и заполните четыре настройки:
| Настройка | Значение | Что означает |
|---|---|---|
| Input Binary Field(s) | orders,readme,meta | Три поля одного item |
| Output Format | Zip | Один ZIP, не три Gzip |
| File Name | ezgpt-demo.zip | Имя скачиваемого архива |
| Put Output File in Field | bundle | Binary-поле с архивом |
Выполните узел. Ожидается один item с binary-полем bundle. Его File Name — ezgpt-demo.zip, File Extension — zip. В проверенном запуске архив занял 507 байт. Не требуйте такой же размер у каждой сборки: важнее содержимое, а ZIP может включать служебные данные и временные метки.
Для маленьких файлов ZIP может оказаться больше суммы исходных файлов из-за заголовков архива. В примере исходные данные занимают 194 байта. Архив здесь нужен для объединения трёх файлов, а не как обещание экономии места.
Шаг 6. Проверьте выход перед распаковкой
В Check ZIP выберите Run Once for All Items, JavaScript и вставьте:
const items = $input.all();
if (items.length !== 1 || Object.keys(items[0].binary || {}).join('|') !== 'bundle') {
throw new Error('zip_binary_field_required');
}
const file = items[0].binary.bundle;
if (file.fileName !== 'ezgpt-demo.zip' || file.fileExtension !== 'zip') {
throw new Error('zip_name_or_extension_invalid');
}
const buffer = await this.helpers.getBinaryDataBuffer(0, 'bundle');
if (buffer.length > 65536 || buffer.subarray(0, 4).toString('hex') !== '504b0304') {
throw new Error('zip_header_or_size_invalid');
}
return items;
Ожидается только binary-поле bundle с именем ezgpt-demo.zip и расширением zip. Затем проверяются размер до 65536 байт и первые четыре байта ZIP с локальной записью файла. Это узкая проверка нашего непустого архива, не универсальный детектор всех ZIP и не проверка безопасности вложений.
Успех Check ZIP не доказывает, что архив не повреждён: в нём ещё может оказаться сломанная структура. Настоящая распаковка произойдёт в следующем Compression. Не подавайте сюда неизвестные архивы только потому, что их первые байты похожи на ZIP.
Шаг 7. Распакуйте архив
В Open ZIP выберите Operation: Decompress. В Input Binary Field(s) укажите bundle, в Output Prefix — file_. Выполните узел от начала цепочки.
Ожидается один item с тремя binary-полями file_0, file_1 и file_2. Это не три items. В проверенном примере они содержат orders.csv, readme.txt и metadata.json соответственно. Но дальнейшую логику не стоит связывать только с номером поля: она будет искать нужное имя файла.
| File Name | Размер UTF-8 | Что внутри |
|---|---|---|
| orders.csv | 66 байт | Два вымышленных заказа |
| readme.txt | 83 байта | Учебное пояснение |
| metadata.json | 45 байт | ID пакета и валюта |
После распаковки исходное имя orders.csv остаётся в fileName, хотя binary-поле уже называется file_0. Если поменять Output Prefix на unpacked_, изменятся поля, но не имена файлов. Нашу итоговую проверку это не должно сломать.
Шаг 8. Сравните результат с оригиналом
В Check Roundtrip задайте Run Once for All Items, JavaScript и вставьте:
const items = $input.all();
if (items.length !== 1) throw new Error('one_unpacked_item_required');
const binary = items[0].binary || {};
const expected = $('Check Files').first().json.manifest;
if (Object.keys(binary).length !== expected.length) throw new Error('unpacked_count_mismatch');
const found = new Set();
const files = [];
for (const [field, metadata] of Object.entries(binary)) {
const original = expected.find(file => file.file_name === metadata.fileName);
if (!original || found.has(metadata.fileName)) throw new Error('unpacked_name_mismatch');
const buffer = await this.helpers.getBinaryDataBuffer(0, field);
if (!buffer.equals(Buffer.from(original.text, 'utf8'))) throw new Error('unpacked_content_mismatch');
found.add(metadata.fileName);
files.push({ file_name: metadata.fileName, bytes: buffer.length, matches_original: true });
}
files.sort((a, b) => a.file_name.localeCompare(b.file_name));
return [{ json: {
batch_id: $('Check Files').first().json.batch_id,
archive_name: 'ezgpt-demo.zip',
file_count: files.length,
files,
roundtrip_ok: true,
action_performed: false,
} }];
Проверяются количество распакованных файлов, точные имена без повторов и полное совпадение каждого буфера с исходным UTF-8-текстом из Check Files. Это сильнее проверки только размера: два разных текста могут занимать одинаковое число байт. Порядок binary-полей не важен.
Финальный files сортируется по имени только ради удобного вывода. matches_original true означает совпадение с эталоном данного выполнения. Если перед запуском вы сами измените учебный текст, эталон тоже изменится. Это контроль упаковки и распаковки, не доказательство правильности или подлинности исходного документа.
Шаг 9. Выполните всю схему
Нажмите Execute Workflow. Каждый узел должен обработать один item. Последний вернёт такой JSON:
{
"batch_id": "zip-demo-001",
"archive_name": "ezgpt-demo.zip",
"file_count": 3,
"files": [
{
"file_name": "metadata.json",
"bytes": 45,
"matches_original": true
},
{
"file_name": "orders.csv",
"bytes": 66,
"matches_original": true
},
{
"file_name": "readme.txt",
"bytes": 83,
"matches_original": true
}
],
"roundtrip_ok": true,
"action_performed": false
}
Проверьте file_count 3, roundtrip_ok true и три matches_original true. action_performed false означает, что схема не отправляла файлы и не записывала их в ваши рабочие папки. Создание файлов и архива внутри хранилища n8n при этом действительно происходило.
Чтобы получить сам архив, откройте выход Make ZIP, переключитесь на Binary и скачайте bundle средствами интерфейса. В Check Roundtrip бинарные файлы намеренно не переносятся: там только отчёт. Наличие отчёта не означает, что архив отправлен клиенту или загружен в облако.
Шаг 10. Проверьте отсутствие файла и неверное имя
В Make ZIP временно уберите meta из Input Binary Field(s), оставив orders,readme. Полный запуск должен дойти до Check Roundtrip и остановиться с unpacked_count_mismatch: архив создан, но в него попали только два файла. Верните orders,readme,meta.
Теперь в Sample Files временно замените у записи metadata.json значение name на readme.txt. Check Files должен остановиться с file_name_missing_or_duplicate ещё до упаковки. Верните metadata.json. Так проверяется, что одинаковое имя не приводит к незаметной потере содержимого.
Если вместо списка binary-полей указать в Make ZIP поле data, ошибка должна возникнуть в самом Make ZIP: такого входного поля в схеме нет. Не переименовывайте всё подряд ради устранения ошибки; сверяйте именно binary-поля предыдущего узла.
Шаг 11. Проверьте независимость от номера поля
В Open ZIP временно замените Output Prefix file_ на unpacked_. После полного запуска итог должен совпасть с шагом 9. Check Roundtrip читает все binary-поля и сопоставляет их по fileName, поэтому не зависит от префикса. После теста верните file_.
Можно также изменить текст readme.txt в Sample Files, сохранив его имя. Схема должна успешно обработать новый текст, а число bytes для readme.txt изменится. Это ожидаемо: эталон создаётся из текущего входа до Make ZIP. Для точного результата из примера верните исходную строку вместе с завершающим переносом.
Шаг 12. Восстановите и сохраните рабочий вариант
Верните три исходных файла, Input Binary Field(s) orders,readme,meta, формат Zip, имя ezgpt-demo.zip, выход bundle и префикс file_. Сохраните workflow и выполните его ещё раз. Сверьте не только зелёный статус, но и имена, длины, количество и roundtrip_ok.
Если дальше нужен разбор orders.csv, выбирайте распакованный файл по fileName и передавайте его в Extract from File. Не считайте все вложения CSV и не исполняйте их содержимое. Отправку архива, загрузку в облако и запись в рабочую папку добавляют отдельным действием после проверки.
Если результат отличается
| Ошибка | Что произошло | Где проверить |
|---|---|---|
| binary_fields_mismatch | Нет нужного binary-поля или появилось лишнее | Выход Sample Files |
| file_name_missing_or_duplicate | Имя отсутствует или повторяется | name в исходных files |
| zip_binary_field_required | На выходе не только bundle | Формат и выход Make ZIP |
| unpacked_count_mismatch | Распакованы не все три файла | Список входных полей Make ZIP |
| unpacked_content_mismatch | Байты отличаются от эталона | Узлы между Open ZIP и проверкой |
Неверное binary-поле распаковки и повреждённая структура ZIP завершаются ошибкой в Open ZIP. Смена расширения файла не преобразует его формат. Ошибку распаковки нельзя считать подтверждением того, что архив безопасен или что внутри нет других проблем.
Что нельзя доверять этой схеме без доработки
Этот workflow проверяет собственный маленький архив с тремя известными текстовыми файлами. Он не сканирует вложения антивирусом, не проверяет подписи, не работает с паролями и не доказывает безопасность чужих ZIP. Нельзя распаковывать неизвестные архивы в рабочую папку или автоматически открывать и запускать вложенные файлы.
Лимит сжатого ZIP в Check ZIP не гарантирует небольшой размер после распаковки. Защита от архивных бомб, обхода путей, вложенных архивов и ресурсных перегрузок — отдельная задача. В проверенной версии есть собственные ограничения узла, но их конфигурацию и пригодность для ваших файлов мы в этой инструкции не проверяли.
Несколько входных items не объединяются здесь в один общий архив. Пример сознательно требует один item с тремя binary-полями. Другие режимы хранения binary, распределённые workers, большие архивы и n8n Cloud требуют отдельного прогона.
Прочитать CSV через Extract from File
Выгрузить данные n8n в CSV и проверить файл
Остановить workflow при неверных данных
Что проверено редакцией
16 октября 2026 года workflow проверен на n8n 2.42.4 в отдельном Docker-экземпляре с binary-режимом filesystem через ручные запуски API. Все 27 тестов прошли: основной roundtrip, другой порядок binary-полей, изменённый текст, другой префикс распаковки, экспорт ZIP, отсутствующее/лишнее binary-поле, только JSON, два items, повтор/отсутствие/неверное имя, пустой/слишком большой/невалидный UTF-8 файл, неверное поле упаковки, пропуск файла в архиве, другое выходное поле, имя и формат, неверный заголовок/слишком большой ZIP/нет расширения, неверное поле распаковки, повреждённый ZIP, изменение имени и байтов после распаковки. Основной запуск использует точный скачиваемый workflow без подмены кода. Сверены manifest, точные имена и длины, UTF-8, совпадение байтов и отсутствие успешного отчёта при ошибках. Образец ZIP отдельно прочитан стандартной библиотекой Windows: три файла совпадают с исходными байтами. Настройки Compression 1.1 и binary helper сверены с официальной документацией и кодом узла. Редакторские клики и кнопка скачивания, Cloud, другие binary-режимы, большие/защищённые паролем/чужие архивы, другие форматы и полный аудит безопасности не тестировались. Учебный контейнер остановлен, существующие workflows не менялись. Обложка — редакционная AI-иллюстрация.
Частые вопросы
Что вводить в Input Binary Field(s): orders или orders.csv?
Имя binary-поля, в этом примере orders. Имя файла orders.csv хранится в его метаданных. Для трёх файлов введите orders,readme,meta.
Почему после распаковки один item, а файлов три?
Compression в проверенном сценарии кладёт распакованные файлы в разные binary-поля одного item. Их имена начинаются с Output Prefix. Число items не равно числу файлов.
Где скачать ZIP, если в конце только JSON?
Откройте выход Make ZIP и его binary-поле bundle. Check Roundtrip специально возвращает только отчёт о проверке, а не сам архив.
Почему ZIP больше исходных файлов?
У архива есть заголовки и другая служебная информация. Для нескольких очень маленьких файлов она может перевесить экономию от сжатия. Объединение файлов не гарантирует уменьшение размера.
Можно распаковывать архивы клиентов по этой инструкции?
Не автоматически. Пример проверен только на собственных маленьких текстовых файлах. Для чужих архивов нужны ограничения распаковки, изоляция, проверка путей и содержимого, контроль хранения и прав доступа.