При разработке интеграций и обменов часто возникает задача передать на FTP-сервер не один отдельный файл, а целый каталог со сложной структурой подпапок (например, каталог с изображениями товаров). Попытка передать имя папки в метод FTPСоединение.Записать() приводит к ошибке, поскольку протокол FTP и объект платформы 1С рассчитаны на работу с отдельными файлами. Разберем технические особенности работы с FTP в 1С и рассмотрим несколько способов решения этой задачи.
Проанализируем ограничения, с которыми разработчик сталкивается при работе со встроенным механизмом 1С:
FTPСоединение.Записать() принимает только два параметра: путь к конкретному локальному файлу-источнику и путь к целевому файлу на сервере. Передать маску или имя каталога напрямую невозможно.FTPСоединение.СоздатьКаталог() не может создать всю цепочку вложенных папок за один вызов. Если родительского каталога не существует, попытка создать подпапку вызовет исключение. Каталоги необходимо создавать последовательно от корня к конечной вложенности.СоздатьКаталог() также возвращает ошибку, поэтому вызов необходимо оборачивать в конструкцию Попытка ... Исключение либо предварительно проверять структуру через FTPСоединение.НайтиФайлы().Рассмотрим основной способ выгрузки штатными средствами платформы. Чтобы не писать собственную сложную рекурсивную функцию для чтения файлов на локальном диске, воспользуемся методом НайтиФайлы(), передав третьим параметром значение Истина. Это позволит за один вызов получить плоский массив всех файлов во всех вложенных каталогах.
Разберем по шагам алгоритм пофайловой отправки:
FTPСоединение один раз перед началом цикла. Обязательно указываем параметр ПассивныйРежим = Истина, чтобы исключить блокировку соединения брандмауэрами.Посмотрим на пример программной реализации:
Процедура ВыгрузитьКаталогНаFTP(ЛокальныйКаталог, FTPСервер, FTPПорт, FTPПользователь, FTPПароль, FTPКаталогПриемник)
// Инициализируем соединение один раз для всей пачки файлов
FTP = Новый FTPСоединение(FTPСервер, FTPПорт, FTPПользователь, FTPПароль, , Истина, 60);
// Нормализуем пути
Если Не Прав(ЛокальныйКаталог, 1) = "\" И Не Прав(ЛокальныйКаталог, 1) = "/" Тогда
ЛокальныйКаталог = ЛокальныйКаталог + "\";
КонецЕсли;
// Получаем список всех вложенных файлов
МассивФайлов = НайтиФайлы(ЛокальныйКаталог, "*.*", Истина);
ДлинаКорня = СтрДлина(ЛокальныйКаталог);
Для Каждого ФайлИсточника Из МассивФайлов Цикл
Если ФайлИсточника.ЭтоКаталог() Тогда
Продолжить;
КонецЕсли;
// Получаем относительный путь к файлу
ОтносительныйПуть = Сред(ФайлИсточника.ПолноеИмя, ДлинаКорня + 1);
ОтносительныйПуть = СтрЗаменить(ОтносительныйПуть, "\", "/");
// Формируем целевой путь на FTP
ЦелевойПуть = FTPКаталогПриемник + "/" + ОтносительныйПуть;
ЦелевойПуть = СтрЗаменить(ЦелевойПуть, "//", "/");
// Создаем дерево каталогов на сервере при необходимости
СоздатьДеревоКаталоговFTP(FTP, ЦелевойПуть);
// Записываем файл на сервер
FTP.Записать(ФайлИсточника.ПолноеИмя, ЦелевойПуть);
КонецЦикла;
КонецПроцедуры
Процедура СоздатьДеревоКаталоговFTP(FTP, ПолныйЦелевойПуть)
ПозицияСлэша = СтрНайти(ПолныйЦелевойПуть, "/", НаправлениеПоиска.СКонца);
Если ПозицияСлэша = 0 Тогда
Возврат;
КонецЕсли;
ПутьККаталогу = Лев(ПолныйЦелевойПуть, ПозицияСлэша - 1);
ЧастиПути = СтрРазделить(ПутьККаталогу, "/", Ложь);
ТекущийПуть = "";
Для Каждого Часть Из ЧастиПути Цикл
Если ПустаяСтрока(Часть) Тогда
Продолжить;
КонецЕсли;
ТекущийПуть = ТекущийПуть + "/" + Часть;
Попытка
FTP.СоздатьКаталог(ТекущийПуть);
Исключение
// Если каталог уже существует, платформа вызовет исключение, которое мы перехватываем
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Если на стороне FTP-сервера есть возможность настроить автоматическую распаковку файлов (например, через скрипты автоматизации или планировщик cron), наиболее быстрым вариантом будет сжатие каталога в единый ZIP-архив средствами объекта ЗаписьZipФайла.
Передача одного большого архива происходит значительно быстрее, чем отправка сотен мелких изображений по отдельности, так как накладные расходы на сетевые команды FTP для каждого файла сводятся к минимуму.
Если задача выполняется периодически и объем передаваемых данных велик, эффективным решением будет делегирование передачи внешним специализированным утилитам через метод ЗапуститьПриложение().
Рассмотрим преимущества такого подхода:
Посмотрим на пример вызова внешней синхронизации через скрипт WinSCP:
Команда = "winscp.com /command ""open ftp://пользователь:пароль@ftp.example.com/"" ""synchronize remote C:\Images /remote/images"" ""exit""";
ЗапуститьПриложение(Команда, , Истина);
При реализации выгрузки большого количества файлов штатными средствами 1С обратите внимание на следующие правила:
FTPСоединение на каждом файле многократно увеличивает время выгрузки из-за постоянной авторизации и согласования портов.ФоновыеЗадания.Выполнить() предотвращает зависание пользовательского интерфейса и разрыв сессий по таймауту.