При разработке механизмов импорта данных из внешних файлов (например, из Microsoft Excel) в клиент-серверном варианте работы 1С:Предприятие часто возникает досадная ситуация: код, который идеально работал на локальном компьютере программиста («файловая база»), при переносе на сервер начинает выдавать неопределенную «Ошибку при выполнении файловой операции». Эта проблема обычно локализуется в момент вызова метода Прочитать() объекта ТабличныйДокумент.
В этой статье мы подробно разберем, почему возникает эта ошибка, как проанализировать состояние системы в момент сбоя и какие существуют стратегии решения — от исправления прав доступа до изменения архитектуры загрузки данных.
Типовой алгоритм, который мы рассматриваем, состоит из нескольких этапов: выбор файла пользователем на клиенте, помещение его во временное хранилище, передача адреса на сервер, извлечение данных на сервере, сохранение их во временный файл и, наконец, чтение. Проанализируем исходный код, который часто становится источником проблем:
&НаКлиенте
Процедура ИмяФайлаНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Фильтр = "MS Excel|*.xls";
Если Диалог.Выбрать() Тогда
лФайл = Новый ДвоичныеДанные(Диалог.ПолноеИмяФайла);
Адрес = ПоместитьВоВременноеХранилище(лФайл, Новый УникальныйИдентификатор);
ПолучитьРезультат(Адрес);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПолучитьРезультат(Адрес)
ФайлПуть = ПолучитьИмяВременногоФайла("xls");
Данные = ПолучитьИзВременногоХранилища(Адрес);
Данные.Записать(ФайлПуть);
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент.Прочитать(ФайлПуть); // Ошибка возникает здесь
УдалитьФайлы(ФайлПуть);
КонецПроцедуры
Первое, что мы должны проверить — это права доступа. В клиент-серверном варианте код &НаСервере исполняется не от имени текущего пользователя Windows, а от имени системной учетной записи, под которой запущена служба 1C:Enterprise 8.3 Server Agent (обычно это USR1CV8).
Выясним причину: если служба не имеет прав на запись или чтение в директории временных файлов сервера, метод Записать() может создать пустой файл или вовсе завершиться с ошибкой, которую 1С интерпретирует как общую ошибку файловой операции. Проанализируем ситуацию: попробуйте временно заменить путь, возвращаемый ПолучитьИмяВременногоФайла(), на явно указанную общую папку с правами «Для всех», чтобы исключить этот фактор.
Рассмотрим важный нюанс, связанный с расширениями .xls и .xlsx. Метод Прочитать() объекта ТабличныйДокумент чувствителен к расширению временного файла. Если пользователь выбирает файл современного формата (Excel 2007+), который на самом деле является xlsx, а мы жестко задаем временному файлу расширение xls в коде ПолучитьИмяВременногоФайла("xls"), сервер может не справиться с парсингом структуры файла.
Рекомендуем передавать оригинальное расширение файла с клиента на сервер вместе с двоичными данными:
// На клиенте получаем расширение
ФайлИнфо = Новый Файл(Диалог.ПолноеИмяФайла);
Расширение = ФайлИнфо.Расширение; // Содержит ".xls" или ".xlsx"
// Передаем расширение в серверную процедуру
ПолучитьРезультат(Адрес, Расширение);
Посмотрим на ситуацию с точки зрения актуальных стандартов платформы. Начиная с версии 8.3.15, использование синхронных методов работы с файлами в интерфейсе считается устаревшим. Хотя в рассматриваемом примере ошибка возникает на сервере, сам процесс помещения файла рекомендуется перевести на асинхронные рельсы. Рассмотрим использование НачатьПомещениеФайлаНаСервер(). Это более надежный способ, который гарантирует корректную передачу потока данных между слоями системы, особенно в веб-клиенте.
Часто работа в терминальном режиме (RDP) накладывает дополнительные ограничения на маппинг дисков и доступ к временным папкам. Выясним, не блокирует ли файл антивирусное ПО. На серверах часто установлены сканеры, которые «подхватывают» файл сразу после его создания методом Записать(). В этот момент 1С пытается вызвать Прочитать(), но файл оказывается заблокирован антивирусом для проверки. В качестве эксперимента попробуйте добавить небольшую паузу или обернуть чтение в попытку-исключение с несколькими итерациями.
Для Итерация = 1 По 3 Цикл
Попытка
ТабличныйДокумент.Прочитать(ФайлПуть);
Прервать;
Исключение
Если Итерация = 3 Тогда
ВызватьИсключение "Не удалось прочитать файл после 3 попыток: " + ОписаниеОшибки();
КонецЕсли;
// Небольшая пауза перед повтором
КонецПопытки;
КонецЦикла;
Если серверный метод продолжает выдавать ошибку из-за специфических настроек безопасности или ограничений прав, разберем альтернативный подход, предложенный опытными разработчиками: обработка файла на стороне клиента.
Суть метода заключается в том, что мы читаем данные из Excel прямо на компьютере пользователя (где права доступа гарантированно есть) и переносим их в ТаблицуЗначений или Массив. Затем мы передаем уже готовую структуру данных на сервер для дальнейшей записи в базу данных. Рассмотрим преимущества и недостатки:
Преимущества:
Excel.Application, если он установлен у пользователя.Недостатки:
Проанализируем пример кода для клиентской обработки:
&НаКлиенте
Процедура ЗагрузитьНаКлиенте()
// Читаем файл в ТабличныйДокумент прямо на клиенте
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ИмяФайла);
// Преобразуем ТабДок в массив структур для передачи на сервер
ДанныеДляСервера = Новый Массив;
Для НомерСтроки = 1 По ТабДок.ВысотаТаблицы Цикл
СтруктураСтроки = Новый Структура("Артикул, Количество");
СтруктураСтроки.Артикул = ТабДок.Область(НомерСтроки, 1).Текст;
СтруктураСтроки.Количество = ТабДок.Область(НомерСтроки, 2).Текст;
ДанныеДляСервера.Добавить(СтруктураСтроки);
КонецЦикла;
ОбработатьДанныеНаСервере(ДанныеДляСервера);
КонецПроцедуры
Мы проанализировали основные причины возникновения «Ошибки при выполнении файловой операции». Подытожим наши выводы: чаще всего проблема кроется либо в правах доступа учетной записи службы 1С к временным каталогам, либо в некорректном расширении временного файла при вызове метода ПолучитьИмяВременногоФайла(). Если системные ограничения сервера непреодолимы, наиболее стабильным решением остается предварительная обработка данных на стороне клиента с последующей передачей типизированных данных на сервер.