Работа с файлами формата DBF (через встроенный объект XBase) в клиент-серверном варианте работы платформы «1С:Предприятие» имеет ряд важных особенностей. Часто разработчики сталкиваются с ситуацией, когда код, успешно работающий на локальном компьютере в файловой базе, отказывается работать на сервере, возвращая ошибку вида «Файл не обнаружен» или ошибку совместного доступа.
В этой статье мы подробно разберем, почему возникает подобная ошибка при попытке поместить созданный файл DBF во временное хранилище, проанализируем типичные ошибки разработчиков и составим правильный, отказоустойчивый алгоритм выгрузки данных.
Рассмотрим классический пример кода, который часто приводит к сбою на сервере под управлением операционной системы Linux (на что указывает характерный путь к временному каталогу /tmp/):
&НаСервере
Функция Команда2НаСервере()
НашеИмяФайла = "PriceP.dbf";
НашПолныйПутьФайла = КаталогВременныхФайлов() + НашеИмяФайла;
БД = Новый XBase;
// (Здесь инициализируется структура колонок файла)
БД.СоздатьФайл(НашПолныйПутьФайла);
// Заполнение файла данными в цикле
Пока Выборка.Следующий() Цикл
БД.Добавить();
БД.Цена = Выборка.Цена;
БД.Записать();
КонецЦикла;
// Попытка поместить файл во временное хранилище
АдресВХ = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(НашПолныйПутьФайла));
Возврат АдресВХ;
КонецФункции
При выполнении этого кода на платформе Linux сервер возвращает ошибку:
Ошибка при вызове конструктора (ДвоичныеДанные): Файл не обнаружен '/tmp/PriceP.dbf'
Давайте разберем по шагам, почему операционная система «не видит» только что созданный файл, и какие еще критические проблемы скрыты в этом фрагменте кода.
Объект XBase взаимодействует с файловой системой на низком уровне. Когда мы вызываем метод СоздатьФайл(), операционная система открывает дескриптор файла и предоставляет процессу монопольный доступ для записи.
До тех пор, пока мы явно не вызовем метод ЗакрытьФайл(), буферы записи операционной системы могут быть не сброшены на диск окончательно, а сам файл остается заблокированным рабочим процессом 1С (rphost). При попытке инициализировать объект Новый ДвоичныеДанные(ПутьКФайлу) платформа пытается прочитать этот файл с диска, но получает отказ операционной системы или читает пустой/недозаписанный файл. Метод ЗакрытьФайл() является обязательным завершением работы с базой данных dBase.
Если сервер 1С:Предприятия работает под управлением ОС Linux, критически важной становится чувствительность файловой системы к регистру символов (case-sensitivity).
Платформа 1С имеет документированную особенность: при создании файлов формата DBF через объект XBase в среде Linux, расширение создаваемого файла принудительно приводится к верхнему регистру — .DBF, даже если в коде было написано .dbf с маленькой буквы.
Проанализируем этот момент:
/tmp/PriceP.dbf.БД.СоздатьФайл() создает на диске файл с именем: /tmp/PriceP.DBF.Новый ДвоичныеДанные("/tmp/PriceP.dbf")..dbf, не находит его и возвращает ошибку «Файл не обнаружен».Для решения этой проблемы необходимо всегда контролировать регистр расширения при работе на Linux-серверах либо использовать встроенные механизмы генерации путей.
Использование статического имени файла (например, "PriceP.dbf") в общем каталоге временных файлов — это грубая архитектурная ошибка. Если два пользователя одновременно запустят выгрузку прайса, возникнет конфликт совместного доступа к одному и тому же файлу в каталоге /tmp/. Один пользователь заблокирует файл, а второй получит системную ошибку.
Для предотвращения подобных ситуаций на сервере необходимо генерировать уникальное имя файла для каждого сеанса с помощью функции ПолучитьИмяВременногоФайла().
Разберем эталонный и безопасный шаблон выгрузки данных в формат DBF в клиент-серверном режиме работы 1С:Предприятия.
Шаг 1. Генерируем уникальный путь к файлу на сервере
Для генерации уникального имени файла воспользуемся системной функцией. При этом учитываем требования к длине имен в формате dBase (желательно укладываться в стандарт DOS 8.3 для совместимости со старым ПО, хотя сама платформа 1С поддерживает длинные имена файлов).
Шаг 2. Корректно описываем структуру таблицы и создаем файл
Перед вызовом метода СоздатьФайл() обязательно нужно наполнить коллекцию полей Поля, иначе файл не сможет быть инициализирован.
Шаг 3. Заполняем, записываем и обязательно закрываем файл
После завершения цикла записи вызываем методы Записать() и ЗакрытьФайл().
Шаг 4. Помещаем файл во временное хранилище и удаляем его с сервера
После закрытия файла мы можем безопасно прочитать его в двоичные данные, отправить клиенту через временное хранилище и сразу же удалить временный файл с диска сервера, чтобы не засорять систему.
Посмотрим на пример правильной реализации серверной функции:
&НаСервере
Функция СформироватьПрайсDBFНаСервере()
// Шаг 1. Получаем уникальный путь к файлу с расширением в верхнем регистре
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("DBF");
БД = Новый XBase;
// Шаг 2. Обязательно определяем структуру колонок таблицы
БД.Поля.Добавить("CODE", "S", 10);
БД.Поля.Добавить("NAME", "S", 100);
БД.Поля.Добавить("PRICE", "N", 15, 2);
БД.Поля.Добавить("DATE", "D");
// Создаем пустой файл со структурой
БД.СоздатьФайл(ИмяВременногоФайла);
Если Не БД.Открыта() Тогда
ВызватьИсключение "Не удалось создать файл DBF на сервере!";
КонецЕсли;
// Шаг 3. Заполняем таблицу данными
// Для примера выберем условные данные
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК ЦеныНоменклатурыСрезПоследних
| ПО ЦеныНоменклатурыСрезПоследних.Номенклатура = Номенклатура.Ссылка";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
БД.Добавить();
БД.CODE = СокрЛП(Выборка.Код);
БД.NAME = Лев(СокрЛП(Выборка.Наименование), 100);
БД.PRICE = Выборка.Цена;
БД.DATE = ТекущаяДата();
БД.Записать();
КонецЦикла;
// Обязательно закрываем файл для снятия монопольной блокировки ОС
БД.ЗакрытьФайл();
// Шаг 4. Помещаем файл во временное хранилище
АдресВХ = "";
Попытка
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайла);
АдресВХ = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла);
// Удаляем временный файл с диска сервера, так как он больше не нужен
УдалитьФайлы(ИмяВременногоФайла);
Исключение
// В случае ошибки гарантируем удаление временного файла
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Возврат АдресВХ;
КонецФункции
Теперь рассмотрим, как правильно получить этот файл на стороне клиента и предложить пользователю сохранить его на локальный диск. Начиная с платформы 8.3, для интерактивного сохранения файлов рекомендуется использовать асинхронные вызовы:
&НаКлиенте
Процедура ВыгрузитьПрайс(Команда)
// Получаем адрес временного хранилища с сервера
АдресВХ = СформироватьПрайсDBFНаСервере();
Если ПустаяСтрока(АдресВХ) Тогда
Предупреждение("Не удалось сформировать файл выгрузки.");
Возврат;
КонецЕсли;
// Настраиваем диалог сохранения файла на клиенте
ПараметрыПолучения = Новый ПараметрыПолученияФайлаССервера;
ПараметрыПолучения.ИмяФайлаНаКлиенте = "ПрайсЛист.dbf";
ПараметрыПолучения.Заголовок = "Выберите путь для сохранения прайс-листа";
// Запускаем асинхронное получение файла
НачатьПолучениеФайлаССервера(АдресВХ, ПараметрыПолучения);
КонецПроцедуры
Для стабильной работы с DBF-файлами в клиент-серверной архитектуре (особенно на кроссплатформенных серверах 1С под управлением Linux) всегда соблюдайте пять золотых правил:
ЗакрытьФайл() сразу после окончания записи или чтения.ПолучитьИмяВременногоФайла() для предотвращения конфликтов совместного доступа при одновременной работе нескольких пользователей."DBF" в верхнем регистре).УдалитьФайлы() в блоке Попытка...Исключение, чтобы избежать переполнения системных дисков.