<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DOKIVO (Докиво)</title>
    <description>The latest articles on DEV Community by DOKIVO (Докиво) (dokivo).</description>
    <link>https://dev.to/dokivo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15678%2F134fc795-64bb-4378-b12f-09b9f1652d53.png</url>
      <title>DEV Community: DOKIVO (Докиво)</title>
      <link>https://dev.to/dokivo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dokivo"/>
    <language>en</language>
    <item>
      <title>Водяной знак вышел за границы PDF: почему проверка текста этого не заметила</title>
      <dc:creator>Dmitry K</dc:creator>
      <pubDate>Fri, 30 Oct 2026 11:56:58 +0000</pubDate>
      <link>https://dev.to/dokivo/vodianoi-znak-vyshiel-za-ghranitsy-pdf-pochiemu-provierka-tieksta-etogho-nie-zamietila-3pd4</link>
      <guid>https://dev.to/dokivo/vodianoi-znak-vyshiel-za-ghranitsy-pdf-pochiemu-provierka-tieksta-etogho-nie-zamietila-3pd4</guid>
      <description>&lt;p&gt;&lt;em&gt;Команда DOKIVO (Докиво)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;В цепочке «изображение → PDF → пометка “Копия”» оба шага завершились успешно, результат скачался, а извлечение текста вернуло нужное слово. Но на маленькой странице часть надписи оказалась за краем. Рассказываем, как разделили проверку содержимого и проверку его видимости.&lt;/p&gt;

&lt;p&gt;Это разбор исправления в DOKIVO от 30 октября 2026 года. Примеры взяты из нашей контролируемой проверки, а не из документов клиентов.&lt;/p&gt;

&lt;h2&gt;
  
  
  Как получился ложный успех
&lt;/h2&gt;

&lt;p&gt;У проблемного PDF страница была размером 120 × 80 пунктов. Это размеры страницы документа, не ширина окна браузера и не разрешение исходной фотографии.&lt;/p&gt;

&lt;p&gt;Для центральной надписи использовался фиксированный размер шрифта 28 пунктов. Размещение, которое выглядит нормально на большом листе, оказалось неподходящим для маленькой страницы. В PDF при этом сохранялся весь текст «Копия» — наличие символов в документе не означало, что они целиком попадали в видимую область.&lt;/p&gt;

&lt;p&gt;Проверка «нашли нужную строку в извлечённом тексте» ответила на свой вопрос правильно. Ошибка была в выводе: из наличия строки мы ожидали её полной видимости.&lt;/p&gt;

&lt;h2&gt;
  
  
  Исправление: учитывать страницу и ширину строки
&lt;/h2&gt;

&lt;p&gt;Для центральной пометки мы использовали уже существующий в проекте расчёт размещения относительно страницы. Он учитывает ширину и высоту страницы, угол наклона и ширину конкретного текста в выбранном шрифте. В обработчике PDF дополнительно сохранён верхний предел размера шрифта — 28 пунктов.&lt;/p&gt;

&lt;p&gt;Числа символов для такой задачи недостаточно: «ШШШ» и «111» могут занимать разную ширину. В pdf-lib ширину строки можно получить методом &lt;a href="https://pdf-lib.js.org/docs/api/classes/pdffont#widthoftextatsize" rel="noopener noreferrer"&gt;widthOfTextAtSize&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Для понимания геометрии полезна простая модель. Пусть неповёрнутый прямоугольник текста имеет ширину &lt;code&gt;w&lt;/code&gt;, высоту &lt;code&gt;h&lt;/code&gt;, а угол поворота равен &lt;code&gt;a&lt;/code&gt;. Его габариты после поворота:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rotated_width  = abs(w * cos(a)) + abs(h * sin(a))
rotated_height = abs(w * sin(a)) + abs(h * cos(a))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;По этим габаритам можно ограничить масштаб и затем разместить надпись с запасом от краёв. Однако высота прямоугольника — лишь приближение: базовая линия, выносные элементы и метрики конкретных глифов требуют проверки конечного PDF. Формулы выше объясняют принцип, но не заменяют проверку реального шрифта.&lt;/p&gt;

&lt;h2&gt;
  
  
  Что проверять в готовом файле
&lt;/h2&gt;

&lt;p&gt;Мы разделили проверки на три уровня.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Содержимое.&lt;/strong&gt; В документе есть ожидаемое слово. Это ловит потерянную или неверную пометку, но само по себе не ловит обрезание.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Геометрия.&lt;/strong&gt; Получаем прямоугольники символов из готового PDF и сравниваем с границами страницы. Если хотя бы один символ выходит за край, проверка завершается ошибкой. Для извлечения границ подходит, например, &lt;a href="https://pypdfium2.readthedocs.io/en/stable/python_api.html#pypdfium2._helpers.textpage.PdfTextPage.get_charbox" rel="noopener noreferrer"&gt;PdfTextPage.get_charbox&lt;/a&gt; в pypdfium2. При этом координаты символов и границы страницы нужно привести к одной системе координат.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;для каждого символа пометки:
    проверить left &amp;gt;= 0 и bottom &amp;gt;= 0
    проверить right &amp;lt;= page_width и top &amp;lt;= page_height
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это упрощённый пример для страницы без дополнительного поворота и смещённой области обрезки. Для произвольного PDF следует отдельно учитывать поворот и действующую видимую область. Переносить такой псевдокод в обработчик любых документов без этих оговорок нельзя.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Видимость.&lt;/strong&gt; Рендерим страницу и осматриваем изображение: надпись читается, исходная картинка сохранена, пометка не закрывает важную часть документа. Даже правильные прямоугольники символов не доказывают достаточный контраст.&lt;/p&gt;

&lt;h2&gt;
  
  
  Что получилось в нашем случае
&lt;/h2&gt;

&lt;p&gt;После исправления повторили полный путь в браузере: загрузка изображения, создание PDF, добавление «Копия», скачивание результата. Сделали два запуска — при ширине интерфейса 1440 и 390 пикселей. Это проверка двух размеров интерфейса, не испытание физического телефона.&lt;/p&gt;

&lt;p&gt;В обоих скачанных PDF:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;страница сохранила размер 120 × 80 пунктов;&lt;/li&gt;
&lt;li&gt;все пять символов «Копия» оказались внутри страницы;&lt;/li&gt;
&lt;li&gt;проверка структуры qpdf прошла;&lt;/li&gt;
&lt;li&gt;при просмотре рендера исходное изображение и полная пометка были видны.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;В отдельных автоматических тестах проверили границы глифов для короткой и 120-символьной надписи на маленьких и узких страницах. Это ограниченный набор проверок размещения, а не доказательство одинаковой читаемости любых надписей на любых документах. Длинный текст может поместиться, но стать слишком мелким.&lt;/p&gt;

&lt;h2&gt;
  
  
  Проверка, которую стоит оставить в обработчике
&lt;/h2&gt;

&lt;p&gt;Успешный статус задачи, корректная структура PDF и найденная строка отвечают на разные вопросы. Для функции, которая наносит видимую пометку, нужны ещё границы символов и просмотр итоговой страницы.&lt;/p&gt;

&lt;p&gt;Мы оставили маленькие и узкие страницы в проверках, чтобы дальнейшие изменения не вернули обрезание. Проверять только привычный большой лист для такой функции недостаточно.&lt;/p&gt;

&lt;p&gt;Этот случай возник при разработке &lt;a href="https://dokivo.ru/workspace/?workflow=automation" rel="noopener noreferrer"&gt;готовых сценариев DOKIVO&lt;/a&gt;. Сценарий объединяет последовательные операции над файлом; качество результата каждой операции всё равно нужно проверять отдельно.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Текст подготовлен с помощью ИИ по исходникам и сохранённым результатам проверки команды DOKIVO. Описанный результат относится к указанным контрольным файлам.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>pdf</category>
    </item>
    <item>
      <title>Лимит изображения в пикселях и лимит в байтах — две разные проверки</title>
      <dc:creator>Dmitry K</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:50:57 +0000</pubDate>
      <link>https://dev.to/dokivo/limit-izobrazhieniia-v-piksieliakh-i-limit-v-baitakh-dvie-raznyie-provierki-pfa</link>
      <guid>https://dev.to/dokivo/limit-izobrazhieniia-v-piksieliakh-i-limit-v-baitakh-dvie-raznyie-provierki-pfa</guid>
      <description>&lt;p&gt;Форма загрузки может отклонить небольшое по весу изображение из-за ширины. Или принять ширину, но отклонить слишком большой файл. Эти ограничения относятся к разным свойствам, поэтому изменение одного не гарантирует выполнение другого.&lt;/p&gt;

&lt;p&gt;В DOKIVO (Докиво) &lt;a href="https://dokivo.ru/tools/resize-image/" rel="noopener noreferrer"&gt;«Изменить размер»&lt;/a&gt; вписывает JPG, PNG или WebP в заданные границы с сохранением пропорций. Разберём, как принять такой результат для сайта, каталога или формы загрузки.&lt;/p&gt;

&lt;h2&gt;
  
  
  Зафиксируйте оба ограничения
&lt;/h2&gt;

&lt;p&gt;Запишите допустимые ширину, высоту, формат и размер файла. Уточните, требуется ли точный размер или достаточно не превышать границу. «Не шире 1200 пикселей» и «ровно 1200 × 1200» — разные требования.&lt;/p&gt;

&lt;p&gt;Проверьте исходное изображение. Если оно маленькое, формальное увеличение не добавит деталей. В текущем инструменте DOKIVO исходник не увеличивается: он вписывается в заданные ограничения.&lt;/p&gt;

&lt;h2&gt;
  
  
  Рассчитайте ожидаемые пропорции
&lt;/h2&gt;

&lt;p&gt;Для исходника 2400 × 1600 и предела 1200 × 1200 коэффициент уменьшения равен 0,5. Ожидаемый результат вписывания — 1200 × 800, а не квадрат 1200 × 1200. Это расчёт пропорций, не результат проведённой конвертации.&lt;/p&gt;

&lt;p&gt;В общем случае для двух ограничений берётся меньший коэффициент по ширине и высоте, без увеличения исходника. Впишется вся картинка, но одна из сторон может оказаться короче предельной. Если нужен именно квадрат, потребуется отдельное решение об обрезке или полях.&lt;/p&gt;

&lt;h2&gt;
  
  
  Настройте преобразование
&lt;/h2&gt;

&lt;p&gt;В DOKIVO ширина обязательна, высота задаёт дополнительную границу. Добавьте изображение, укажите ширину, при необходимости высоту и откройте сравнение до и после. Смотрите на надписи, тонкие линии и мелкие детали объекта.&lt;/p&gt;

&lt;p&gt;Уменьшение числа пикселей не задаёт точный размер файла в байтах. На объём влияют формат, содержимое и параметры кодирования. Не обещайте получателю «меньше 500 КБ» только по расчёту ширины: нужно проверить фактический файл.&lt;/p&gt;

&lt;h2&gt;
  
  
  Приёмка состоит из двух этапов
&lt;/h2&gt;

&lt;p&gt;Сначала откройте скачанное изображение и проверьте его ширину, высоту, пропорции и читаемость. Затем проверьте размер файла и попробуйте обычную загрузку в целевую форму. Успешное преобразование ещё не означает, что форма примет все остальные свойства.&lt;/p&gt;

&lt;p&gt;Сохраните оригинал. Если уменьшенная копия оказалась слишком мелкой для другого канала, создавайте следующий вариант из исходника. Это сохраняет понятную цепочку версий и не заставляет пытаться восстановить потерянные детали.&lt;/p&gt;

&lt;p&gt;Материал команды DOKIVO, подготовленный с помощью ИИ. Описание инструмента сверено 29 октября 2026 года. Геометрический пример расчётный; заявлений о новом испытании скорости или качества нет.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Сравнение двух XLSX: сначала ключ строки, потом список изменений</title>
      <dc:creator>Dmitry K</dc:creator>
      <pubDate>Thu, 29 Oct 2026 14:49:23 +0000</pubDate>
      <link>https://dev.to/dokivo/sravnieniie-dvukh-xlsx-snachala-kliuch-stroki-potom-spisok-izmienienii-2b2n</link>
      <guid>https://dev.to/dokivo/sravnieniie-dvukh-xlsx-snachala-kliuch-stroki-potom-spisok-izmienienii-2b2n</guid>
      <description>&lt;p&gt;Сравнение ячейки A2 в старой и новой выгрузках легко даёт ложные изменения, если строки просто отсортировали иначе. Для проверки бизнес-данных сначала нужно установить соответствие записей, а уже затем сравнивать значения.&lt;/p&gt;

&lt;p&gt;Мы — команда DOKIVO (Докиво). В &lt;a href="https://dokivo.ru/tools/compare-spreadsheets/" rel="noopener noreferrer"&gt;«Сравнить таблицы Excel»&lt;/a&gt; используем явно заданные пары столбцов и от одного до трёх ключей. Статья объясняет границы такой сверки; это не новый тест скорости или точности.&lt;/p&gt;

&lt;h2&gt;
  
  
  Выберите идентичность записи
&lt;/h2&gt;

&lt;p&gt;Для товарной выгрузки ключом может быть артикул. Если одна и та же позиция встречается на нескольких складах, одного артикула недостаточно: понадобится сочетание «артикул + склад». Ключ должен соответствовать смыслу строки.&lt;/p&gt;

&lt;p&gt;Не включайте в ключ поле, изменение которого хотите найти. Если цена входит в идентификатор, изменение цены может превратиться в исчезновение одной записи и появление другой. Это проблема постановки сравнения, а не обязательно ошибка программы.&lt;/p&gt;

&lt;h2&gt;
  
  
  Явно сопоставьте столбцы
&lt;/h2&gt;

&lt;p&gt;В DOKIVO выбираются исходный и новый XLSX, по одному листу, строки заголовков и пары столбцов. Проверьте направление: «исходный → новый» определяет смысл добавленных и удалённых записей.&lt;/p&gt;

&lt;p&gt;Если в новом файле столбец переименовали, сопоставьте его по содержанию. Совпадение позиции столбца не гарантирует одинаковую семантику. Прежде чем принимать отчёт, проверьте одну известную неизменную запись и одно ожидаемое изменение.&lt;/p&gt;

&lt;h2&gt;
  
  
  Не скрывайте неоднозначные ключи
&lt;/h2&gt;

&lt;p&gt;Пустые и повторяющиеся ключи DOKIVO показывает как неоднозначность. Автоматического слияния дубликатов нет. Решение «взять первую строку» может потерять реальные данные, поэтому причину повторов нужно разобрать в исходной таблице.&lt;/p&gt;

&lt;p&gt;Типы, ведущие нули, пробелы и регистр не нормализуются автоматически. Значения &lt;code&gt;0012&lt;/code&gt; и &lt;code&gt;12&lt;/code&gt; нельзя объявить равными без знания предметной области. Если нормализация нужна, зафиксируйте её как отдельное преобразование и сохраните исходные файлы.&lt;/p&gt;

&lt;h2&gt;
  
  
  Прочитайте полный отчёт
&lt;/h2&gt;

&lt;p&gt;Предварительная сводка и первые изменения помогают проверить настройку. Скачанный XLSX содержит полный отчёт с листами «Сводка» и «Изменения», исходными адресами ячеек, формулами и имеющимся кешем.&lt;/p&gt;

&lt;p&gt;Формулы не пересчитываются, оформление не сравнивается. Следовательно, отчёт не подтверждает актуальность вычисленных итогов и визуальную идентичность книг. Если важен именно пересчёт, его нужно выполнить и проверить в соответствующей табличной среде.&lt;/p&gt;

&lt;p&gt;Хорошая приёмка включает четыре вещи: верно выбраны версии, ключ соответствует записи, неоднозначности разобраны, найденные изменения проверены на исходных адресах. Одного большого числа в сводке недостаточно для решения, какая выгрузка правильная.&lt;/p&gt;

&lt;p&gt;Материал команды DOKIVO, подготовленный с помощью ИИ. Контракт публичного инструмента сверён 29 октября 2026 года. Значения и сценарий иллюстративные; независимый бенчмарк не проводился.&lt;/p&gt;

</description>
      <category>data</category>
    </item>
    <item>
      <title>Повтор запроса к файловому API: почему один XLSX ещё не означает одну операцию</title>
      <dc:creator>Dmitry K</dc:creator>
      <pubDate>Wed, 28 Oct 2026 15:05:08 +0000</pubDate>
      <link>https://dev.to/dokivo/povtor-zaprosa-k-failovomu-api-pochiemu-odin-xlsx-ieshchio-nie-oznachaiet-odnu-opieratsiiu-4m8g</link>
      <guid>https://dev.to/dokivo/povtor-zaprosa-k-failovomu-api-pochiemu-odin-xlsx-ieshchio-nie-oznachaiet-odnu-opieratsiiu-4m8g</guid>
      <description>&lt;p&gt;Автор: Команда DOKIVO (Докиво)&lt;/p&gt;

&lt;p&gt;Клиент отправил PDF на обработку, но не получил ответ: соединение оборвалось. Повторить запрос кажется естественным решением. Однако сервер мог уже принять файл. Новый запрос с новым идентификатором тогда создаст вторую задачу, а два уведомления о результате могут привести к двум записям в учётной системе.&lt;/p&gt;

&lt;p&gt;При проверке нашего файлового API мы разделили четыре события: намерение клиента, запуск обработки, доставку уведомления и запись результата у получателя. Для каждого нужна своя проверка. Даже совпадение двух скачанных файлов не доказывает, что вся цепочка выполнилась ровно один раз.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ключ относится к намерению, а не к HTTP-попытке
&lt;/h2&gt;

&lt;p&gt;Намерение — это конкретный набор файлов, параметры и версия сценария. Ключ для него нужно сохранить до первого запроса. Если создавать новый UUID при каждом сетевом повторе, сервер увидит разные операции.&lt;/p&gt;

&lt;p&gt;В проверенном нами контракте вместе с ключом сохраняются время создания запроса, версия сценария, порядок и имена файлов. Сервер также сравнивает их содержимое и параметры. Получается следующий контракт:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Действие клиента&lt;/th&gt;
&lt;th&gt;Ожидаемый результат&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Первый запрос с новым ключом&lt;/td&gt;
&lt;td&gt;Новый запуск&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Точный повтор с прежним ключом&lt;/td&gt;
&lt;td&gt;Тот же идентификатор запуска&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Изменённый файл с прежним ключом&lt;/td&gt;
&lt;td&gt;Конфликт &lt;code&gt;409&lt;/code&gt;, а не молчаливая новая обработка&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Новое осознанное задание&lt;/td&gt;
&lt;td&gt;Новое намерение и новый ключ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Это важно и для n8n. Повтор узла внутри одного выполнения и запуск всего workflow заново — разные ситуации. Во втором случае источник должен передать сохранённое намерение вместе со своим стабильным идентификатором события. Иначе даже правильно устроенный шаблон создаст новое задание.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Потерянный ответ — отдельное состояние
&lt;/h2&gt;

&lt;p&gt;После тайм-аута нельзя автоматически считать, что запрос не принят. В нашем контракте предусмотрено чтение запуска по прежнему ключу: &lt;code&gt;GET /api/v1/runs/by-key&lt;/code&gt; с заголовком &lt;code&gt;Idempotency-Key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Схема восстановления клиента выглядит так:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;сохранить намерение
отправить запрос один раз
если получен runId:
    читать состояние этого запуска
если исход отправки неизвестен:
    искать запуск по сохранённому ключу
    если найден — продолжить чтение
    если не найден за ограниченное число проверок:
        сохранить состояние «исход неизвестен»
        не создавать новый ключ автоматически
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это схема поведения, а не готовый SDK. В частности, &lt;code&gt;404&lt;/code&gt; при первом поиске не всегда позволяет заключить, что первоначальная отправка точно не состоялась. В проверенном шаблоне число проверок ограничено; неопределённость сохраняется явно, вместо бесконечного цикла или повторной обработки вслепую.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Готовый файл может требовать проверки
&lt;/h2&gt;

&lt;p&gt;В приёмочном примере обрабатывались три учебных PDF-спецификации. Результат содержал четыре извлечённые строки: два файла включены, один исключён из-за несовпадения схемы. XLSX был доступен, но запуск имел состояние &lt;code&gt;needs_attention&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Для интеграции это отдельная ветка: сохранить отчёт и направить его на проверку. Условие «файл скачался» не должно автоматически превращать частично обработанную партию в полностью принятую.&lt;/p&gt;

&lt;p&gt;Содержимое XLSX мы читали отдельной библиотекой. Проверяли не только число строк, но и типы значений: артикулы с ведущими нулями оставались текстом, строка, похожая на формулу, не становилась исполняемой формулой, числовые произведения совпадали с ожидаемыми. Это проверка конкретного корпуса, а не обещание корректно разобрать любую таблицу.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Подпись уведомления не устраняет дубли
&lt;/h2&gt;

&lt;p&gt;Уведомление может прийти повторно, например если получатель успел принять его, но отправитель не увидел успешный HTTP-ответ.&lt;/p&gt;

&lt;p&gt;В проверенном контракте подпись вычисляется как &lt;code&gt;HMAC-SHA256(secret, timestamp + "." + rawBody)&lt;/code&gt;. Получатель проверяет исходные байты тела, время и идентификатор события. Повторная сериализация уже разобранного JSON для вычисления подписи может изменить байты: пробелы и переносы тоже имеют значение.&lt;/p&gt;

&lt;p&gt;Но правильная подпись подтверждает подлинность сообщения, а не отсутствие предыдущего выполнения. Для записи в CRM или базу данных получателю нужна собственная дедупликация. Например, идентификатор события можно фиксировать в таблице входящих событий с уникальным ограничением, а локальное изменение данных выполнять в той же транзакции. Если дальше вызывается ещё один внешний сервис, его повтор и неизвестный исход требуют отдельного решения.&lt;/p&gt;

&lt;p&gt;В наших проверках неверная подпись, устаревшее время и несовпадающий идентификатор были отклонены. Корректное событие и его точный повтор принимались. При этом повтор мог снова скачать тот же XLSX. Однократную запись в чужую CRM этот тест не подтверждал.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Срок хранения ключа и файла — разные сроки
&lt;/h2&gt;

&lt;p&gt;Идентификатор запуска может существовать дольше готового файла. В проверенном контракте метаданные сохранялись семь дней, а результат обработки — 15 минут. Повторное скачивание срок не продлевало.&lt;/p&gt;

&lt;p&gt;Поэтому восстановление спустя несколько часов может корректно найти прежний запуск, но уже не получить его файл. Автоматически запускать обработку заново в этой ситуации — новое бизнес-решение, которое нельзя незаметно спрятать в сетевой retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Что показала приёмка
&lt;/h2&gt;

&lt;p&gt;В сохранённых проверках от 28 октября 2026 года:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;точный повтор API-запроса возвращал прежний &lt;code&gt;runId&lt;/code&gt;, изменённые байты с тем же ключом давали &lt;code&gt;409&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;два выполнения импортированного шаблона в n8n 2.45.4 с сохранённым намерением возвращали тот же запуск и одинаковые байты XLSX;&lt;/li&gt;
&lt;li&gt;исходящий HTTPS webhook пережил два ответа &lt;code&gt;503&lt;/code&gt;, затем получил &lt;code&gt;200&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;результат с частичным включением файлов оставался &lt;code&gt;needs_attention&lt;/code&gt;, а содержимое XLSX проверялось независимо от интерфейса.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Приёмник n8n проверялся в изолированном окружении по loopback, а исходящая HTTPS-доставка — отдельно. Публичную установку n8n клиента и запись в его CRM эти проверки не охватывали. Проверки разных участков нельзя складывать в утверждение о гарантии «ровно один раз» для всей внешней системы.&lt;/p&gt;

&lt;p&gt;Для собственного файлового API полезно начать с трёх аварийных случаев: потерянный ответ после отправки, другой файл с прежним ключом и повтор корректно подписанного уведомления. Они проверяют разные границы и выявляют ошибки, которые обычная успешная конвертация не показывает.&lt;/p&gt;




&lt;p&gt;Материал подготовлен с помощью ИИ по документации и сохранённым результатам проверок проекта. Новые испытания специально для статьи не запускались. Коммерческих предложений и партнёрских ссылок в статье нет.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
