При организации обмена данными через FTP-сервер разработчики часто сталкиваются с неприятной проблемой: регламентное задание считывает файл, который еще не успел полностью скачаться или записаться. В результате во входящем каталоге появляется «битый» файл размером 0 Кб, а процесс интеграции завершается ошибкой. Давайте детально разберем природу этой проблемы и рассмотрим несколько надежных архитектурных и программных решений на платформе 1С:Предприятие 8.3.
Прежде чем переходить к коду, давайте проанализируем, почему возникает подобный конфликт. На первый взгляд, метод FTPСоединение.Получить() является синхронным, то есть код 1С не должен переходить к следующей строке, пока файл не скачан. Однако на практике коллизии возникают по следующим причинам:
Получить(), но скачивает лишь ту часть, которая успела записаться. В итоге файл получается обрезанным или пустым.Рассмотрим подробнее пошаговые сценарии решения этой задачи.
Это самый простой и архитектурно правильный подход. Если у вас нет жесткой необходимости разделять эти процессы, мы рекомендуем отказаться от двух разных регламентных заданий в пользу одного последовательного процесса. В этом случае коллизия совместного доступа исключается физически.
Посмотрим на пример структуры единого регламентного задания:
Процедура ВыполнитьОбменДаннымиЧерезFTP() Экспорт
// Шаг 1: Подключение к FTP
ПараметрыПодключения = ПолучитьПараметрыПодключения();
FTPСоединение = Новый FTPСоединение(ПараметрыПодключения.Сервер,
ПараметрыПодключения.Порт,
ПараметрыПодключения.Пользователь,
ПараметрыПодключения.Пароль);
// Шаг 2: Скачивание файла
ЛокальныйПуть = КаталогВременныхФайлов() + "import.xml";
Попытка
FTPСоединение.Получить("/out/data.xml", ЛокальныйПуть);
Исключение
ЗаписатьВЖурналРегистрации("Обмен FTP. Ошибка скачивания: " + ОписаниеОшибки(),
УровеньЖурналаРегистрации.Ошибка);
Возврат;
КонецПопытки;
// Шаг 3: Обработка файла в этом же потоке
ОбработатьСкачанныйФайл(ЛокальныйПуть);
// Шаг 4: Удаление обработанного файла с FTP и локального диска
Попытка
FTPСоединение.Удалить("/out/data.xml");
УдалитьФайлы(ЛокальныйПуть);
Исключение
// Логируем возможные ошибки очистки каталогов
КонецПопытки;
КонецПроцедуры
Если бизнес-логика требует, чтобы скачиванием и обработкой занимались разные процессы (например, скачивание происходит быстро, а обработка занимает длительное время и должна выполняться в пуле фоновых заданий), мы можем использовать метод атомарного переименования.
Суть метода заключается в том, что первое регламентное задание скачивает файл во временную папку или с "левым" расширением (например, *.tmp). Поскольку второе регламентное задание настроено на обработку только файлов с расширением *.xml, оно его не "увидит". Как только скачивание полностью завершено, первое задание переименовывает файл в целевой формат.
Важное правило: Временный каталог и каталог обработки должны находиться на одном и том же логическом диске сервера (например, на диске D:). В таком случае переименование на уровне файловой системы происходит мгновенно (атомарно) путем изменения записи в таблице MFT, и второе задание никогда не перехватит файл в полузаписанном состоянии.
Разберем пример реализации этого алгоритма:
// В первом регламентном задании (Скачивание)
ПутьВременный = ПутьККаталогуОбмена + "import_temp.tmp";
ПутьИтоговый = ПутьККаталогуОбмена + "import.xml";
Попытка
// Скачиваем файл с временным расширением
FTPСоединение.Получить("data.xml", ПутьВременный);
// Убедимся, что файл записан, и переименуем его
ФайлНаДиске = Новый Файл(ПутьВременный);
Если ФайлНаДиске.Существует() И ФайлНаДиске.Размер() > 0 Тогда
ПереместитьФайл(ПутьВременный, ПутьИтоговый);
// Фиксируем успешное скачивание в Регистре Сведений
ЗаписатьВРегистрФайлСкачан("import.xml");
КонецЕсли;
Исключение
ЗаписатьВЖурналРегистрации("Обмен FTP", УровеньЖурналаРегистрации.Предупреждение, , , ОписаниеОшибки());
КонецПопытки;
Прежде чем второе регламентное задание начнет обрабатывать файл, оно должно убедиться, что операционная система полностью завершила с ним работу и сняла все блокировки записи. Лучший способ проверить это — попытаться открыть файл монопольно. Если файл все еще пишется другим процессом, операционная система вернет ошибку совместного доступа.
Начиная с версии платформы 1С:Предприятие 8.3.9, мы можем использовать объект ФайловыйПоток для проверки блокировки. Если мы можем открыть файл на запись с ограничением совместного доступа СовместныйДоступКФайлу.Нет, значит, файл свободен и полностью записан на диск.
Проанализируем пример функции проверки доступности файла:
Функция ФайлСвободенДляОбработки(ПутьКФайлу)
ФайлПроверки = Новый Файл(ПутьКФайлу);
Если Не ФайлПроверки.Существует() Тогда
Возврат Ложь;
КонецЕсли;
Если ФайлПроверки.Размер() = 0 Тогда
Возврат Ложь; // Файл пустой, обрабатывать нечего
КонецЕсли;
Попытка
// Пытаемся открыть файл монопольно
Поток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть, ДоступКФайлу.Запись, СовместныйДоступКФайлу.Нет);
Поток.Закрыть();
Возврат Истина; // Файл полностью записан и доступен
Исключение
// Файл заблокирован операционной системой (еще пишется)
Возврат Ложь;
КонецПопытки;
КонецФункции
Бывают ситуации, когда из-за нестабильного сетевого соединения метод FTP.Получить() завершается без вызова исключения, но создает пустой или недокачанный файл. Чтобы защититься от таких сбоев, мы должны сверить размер файла на удаленном сервере с размером скачанного файла на локальном диске.
Разберем, как реализовать такое сопоставление:
Функция СкачатьИПроверитьЦелостность(FTPСоединение, ИмяФайлаНаFTP, ЛокальныйПуть)
// Получаем информацию о файле на сервере
НайденныеФайлы = FTPСоединение.НайтиФайлы("/out/", ИмяФайлаНаFTP);
Если НайденныеФайлы.Количество() = 0 Тогда
Возврат Ложь; // Файл отсутствует на сервере
КонецЕсли;
РазмерНаFTP = НайденныеФайлы[0].Размер();
// Скачиваем файл
Попытка
FTPСоединение.Получить("/out/" + ИмяФайлаНаFTP, ЛокальныйПуть);
Исключение
Возврат Ложь;
КонецПопытки;
// Проверяем локальный файл
ЛокальныйФайл = Новый Файл(ЛокальныйПуть);
Если ЛокальныйФайл.Существует() И ЛокальныйФайл.Размер() = РазмерНаFTP Тогда
Возврат Истина; // Размеры совпадают, скачивание прошло успешно
Иначе
// Если размеры отличаются, удаляем поврежденный локальный файл
УдалитьФайлы(ЛокальныйПуть);
Возврат Ложь;
EndIf;
КонецФункции
Самым надежным способом организации интеграций через FTP является предварительная договоренность с разработчиками отправляющей стороны. Попросите их придерживаться следующего стандарта при загрузке данных на ваш FTP:
orders.tmp).orders.xml). На FTP-серверах операция переименования выполняется мгновенно и атомарно.*.xml.Этот подход гарантирует, что регламентное задание 1С никогда не увидит и не начнет скачивать файл, который находится в процессе загрузки.
Для надежного обмена файлами через FTP в 1С всегда комбинируйте несколько уровней защиты. Оптимальным решением будет объединение логики скачивания и разбора в рамках одного регламентного задания. Если это невозможно, используйте скачивание во временный файл с последующим атомарным переименованием и обязательной проверкой размера файла на FTP и локальном диске перед его регистрацией к обработке.