Как в 1С проверить, что файл полностью скачан с FTP-сервера и готов к обработке?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При организации обмена данными через FTP-сервер разработчики часто сталкиваются с неприятной проблемой: регламентное задание считывает файл, который еще не успел полностью скачаться или записаться. В результате во входящем каталоге появляется «битый» файл размером 0 Кб, а процесс интеграции завершается ошибкой. Давайте детально разберем природу этой проблемы и рассмотрим несколько надежных архитектурных и программных решений на платформе 1С:Предприятие 8.3.

Почему возникает проблема файлов размером 0 Кб?

Прежде чем переходить к коду, давайте проанализируем, почему возникает подобный конфликт. На первый взгляд, метод FTPСоединение.Получить() является синхронным, то есть код 1С не должен переходить к следующей строке, пока файл не скачан. Однако на практике коллизии возникают по следующим причинам:

  1. Разделение процессов на разные регламентные задания. Если одно задание скачивает файл, а второе запускается независимо по расписанию и проверяет наличие файлов в папке, велика вероятность, что второе задание попытается прочесть файл в тот момент, когда первое задание его только создает на диске.
  2. Недозагрузка файла отправителем. Внешняя система может медленно выгружать файл на FTP-сервер. Ваша база 1С видит появление файла в списке, начинает его скачивать методом Получить(), но скачивает лишь ту часть, которая успела записаться. В итоге файл получается обрезанным или пустым.
  3. Кэширование и задержки файловой системы ОС. Операционная система сервера (особенно под управлением Windows) не всегда мгновенно снимает блокировку и сбрасывает буферы на диск после завершения сетевой передачи данных.

Рассмотрим подробнее пошаговые сценарии решения этой задачи.

Решение 1. Объединение скачивания и обработки в одно регламентное задание

Это самый простой и архитектурно правильный подход. Если у вас нет жесткой необходимости разделять эти процессы, мы рекомендуем отказаться от двух разных регламентных заданий в пользу одного последовательного процесса. В этом случае коллизия совместного доступа исключается физически.

Посмотрим на пример структуры единого регламентного задания:


Процедура ВыполнитьОбменДаннымиЧерезFTP() Экспорт
    
    // Шаг 1: Подключение к FTP
    ПараметрыПодключения = ПолучитьПараметрыПодключения();
    FTPСоединение = Новый FTPСоединение(ПараметрыПодключения.Сервер, 
                                        ПараметрыПодключения.Порт, 
                                        ПараметрыПодключения.Пользователь, 
                                        ПараметрыПодключения.Пароль);
    
    // Шаг 2: Скачивание файла
    ЛокальныйПуть = КаталогВременныхФайлов() + "import.xml";
    Попытка
        FTPСоединение.Получить("/out/data.xml", ЛокальныйПуть);
    Исключение
        ЗаписатьВЖурналРегистрации("Обмен FTP. Ошибка скачивания: " + ОписаниеОшибки(), 
            УровеньЖурналаРегистрации.Ошибка);
        Возврат;
    КонецПопытки;
    
    // Шаг 3: Обработка файла в этом же потоке
    ОбработатьСкачанныйФайл(ЛокальныйПуть);
    
    // Шаг 4: Удаление обработанного файла с FTP и локального диска
    Попытка
        FTPСоединение.Удалить("/out/data.xml");
        УдалитьФайлы(ЛокальныйПуть);
    Исключение
        // Логируем возможные ошибки очистки каталогов
    КонецПопытки;

КонецПроцедуры

Решение 2. Скачивание во временный файл и атомарное переименование

Если бизнес-логика требует, чтобы скачиванием и обработкой занимались разные процессы (например, скачивание происходит быстро, а обработка занимает длительное время и должна выполняться в пуле фоновых заданий), мы можем использовать метод атомарного переименования.

Суть метода заключается в том, что первое регламентное задание скачивает файл во временную папку или с "левым" расширением (например, *.tmp). Поскольку второе регламентное задание настроено на обработку только файлов с расширением *.xml, оно его не "увидит". Как только скачивание полностью завершено, первое задание переименовывает файл в целевой формат.

Важное правило: Временный каталог и каталог обработки должны находиться на одном и том же логическом диске сервера (например, на диске D:). В таком случае переименование на уровне файловой системы происходит мгновенно (атомарно) путем изменения записи в таблице MFT, и второе задание никогда не перехватит файл в полузаписанном состоянии.

Разберем пример реализации этого алгоритма:


// В первом регламентном задании (Скачивание)
ПутьВременный = ПутьККаталогуОбмена + "import_temp.tmp";
ПутьИтоговый = ПутьККаталогуОбмена + "import.xml";

Попытка
    // Скачиваем файл с временным расширением
    FTPСоединение.Получить("data.xml", ПутьВременный);
    
    // Убедимся, что файл записан, и переименуем его
    ФайлНаДиске = Новый Файл(ПутьВременный);
    Если ФайлНаДиске.Существует() И ФайлНаДиске.Размер() > 0 Тогда
        ПереместитьФайл(ПутьВременный, ПутьИтоговый);
        // Фиксируем успешное скачивание в Регистре Сведений
        ЗаписатьВРегистрФайлСкачан("import.xml");
    КонецЕсли;
Исключение
    ЗаписатьВЖурналРегистрации("Обмен FTP", УровеньЖурналаРегистрации.Предупреждение, , , ОписаниеОшибки());
КонецПопытки;

Решение 3. Проверка монопольного доступа к файлу (Тест на блокировку операционной системой)

Прежде чем второе регламентное задание начнет обрабатывать файл, оно должно убедиться, что операционная система полностью завершила с ним работу и сняла все блокировки записи. Лучший способ проверить это — попытаться открыть файл монопольно. Если файл все еще пишется другим процессом, операционная система вернет ошибку совместного доступа.

Начиная с версии платформы 1С:Предприятие 8.3.9, мы можем использовать объект ФайловыйПоток для проверки блокировки. Если мы можем открыть файл на запись с ограничением совместного доступа СовместныйДоступКФайлу.Нет, значит, файл свободен и полностью записан на диск.

Проанализируем пример функции проверки доступности файла:


Функция ФайлСвободенДляОбработки(ПутьКФайлу)
    
    ФайлПроверки = Новый Файл(ПутьКФайлу);
    Если Не ФайлПроверки.Существует() Тогда
        Возврат Ложь;
    КонецЕсли;
    
    Если ФайлПроверки.Размер() = 0 Тогда
        Возврат Ложь; // Файл пустой, обрабатывать нечего
    КонецЕсли;
    
    Попытка
        // Пытаемся открыть файл монопольно
        Поток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть, ДоступКФайлу.Запись, СовместныйДоступКФайлу.Нет);
        Поток.Закрыть();
        Возврат Истина; // Файл полностью записан и доступен
    Исключение
        // Файл заблокирован операционной системой (еще пишется)
        Возврат Ложь;
    КонецПопытки;
    
КонецФункции

Решение 4. Сравнение размеров файлов: FTP-сервер против Локального диска

Бывают ситуации, когда из-за нестабильного сетевого соединения метод FTP.Получить() завершается без вызова исключения, но создает пустой или недокачанный файл. Чтобы защититься от таких сбоев, мы должны сверить размер файла на удаленном сервере с размером скачанного файла на локальном диске.

Разберем, как реализовать такое сопоставление:


Функция СкачатьИПроверитьЦелостность(FTPСоединение, ИмяФайлаНаFTP, ЛокальныйПуть)
    
    // Получаем информацию о файле на сервере
    НайденныеФайлы = FTPСоединение.НайтиФайлы("/out/", ИмяФайлаНаFTP);
    Если НайденныеФайлы.Количество() = 0 Тогда
        Возврат Ложь; // Файл отсутствует на сервере
    КонецЕсли;
    
    РазмерНаFTP = НайденныеФайлы[0].Размер();
    
    // Скачиваем файл
    Попытка
        FTPСоединение.Получить("/out/" + ИмяФайлаНаFTP, ЛокальныйПуть);
    Исключение
        Возврат Ложь;
    КонецПопытки;
    
    // Проверяем локальный файл
    ЛокальныйФайл = Новый Файл(ЛокальныйПуть);
    Если ЛокальныйФайл.Существует() И ЛокальныйФайл.Размер() = РазмерНаFTP Тогда
        Возврат Истина; // Размеры совпадают, скачивание прошло успешно
    Иначе
        // Если размеры отличаются, удаляем поврежденный локальный файл
        УдалитьФайлы(ЛокальныйПуть);
        Возврат Ложь;
    EndIf;
    
КонецФункции

Решение 5. Двухэтапная загрузка на стороне отправителя (Золотой стандарт FTP)

Самым надежным способом организации интеграций через FTP является предварительная договоренность с разработчиками отправляющей стороны. Попросите их придерживаться следующего стандарта при загрузке данных на ваш FTP:

  1. Отправитель загружает файл под временным именем (например, orders.tmp).
  2. После того как загрузка полностью завершена и поток закрыт, отправитель выполняет команду переименования файла на FTP-сервере в целевое имя (например, orders.xml). На FTP-серверах операция переименования выполняется мгновенно и атомарно.
  3. Ваша база 1С настраивается на получение файлов исключительно по маске *.xml.

Этот подход гарантирует, что регламентное задание 1С никогда не увидит и не начнет скачивать файл, который находится в процессе загрузки.

Резюме

Для надежного обмена файлами через FTP в 1С всегда комбинируйте несколько уровней защиты. Оптимальным решением будет объединение логики скачивания и разбора в рамках одного регламентного задания. Если это невозможно, используйте скачивание во временный файл с последующим атомарным переименованием и обязательной проверкой размера файла на FTP и локальном диске перед его регистрацией к обработке.

← На главную