При разработке на платформе 1С часто возникает задача обработки файлов Excel на стороне сервера. Нередко программисты начинают с того, что ищут готовый шаблон простой обработки загрузки из excel, чтобы быстро реализовать импорт. Однако стандартный подход с использованием COM-объекта может преподнести неприятный сюрприз: код, который отлично работает на клиенте, на сервере завершается с ошибкой "Приложению Microsoft Excel не удается получить доступ к файлу...". Давайте разберем, почему это происходит и как это исправить, рассмотрев несколько подходов — от исправления окружения до полного отказа от COM.
Первое, что нужно понять — это различие между клиентским и серверным контекстом. Когда пользователь запускает обработку на своем компьютере (клиенте), его локальные диски, такие как D:, доступны. Однако сервер 1С — это отдельный процесс, часто на другой машине, который ничего не знает о локальных дисках пользователя.
Код, подобный этому, обречен на провал в серверном вызове:
&НаСервере
Процедура ОткрытьНаСервере()
// Этот путь существует только на клиентской машине, но не на сервере!
ИмяФайла = "D:\1C\Обработки\f1.xlsx";
Попытка
Excel = Новый COMОбъект("Excel.Application");
WB = Excel.Workbooks.Open(ИмяФайла); // Ошибка!
// ...
Исключение
Сообщить(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Правильный подход — передать сам файл с клиента на сервер. Для этого в платформе 1С существует специальный механизм временного хранилища. Разберем по шагам, как это реализовать.
Посмотрим на пример кода, который реализует этот алгоритм:
// Шаг 1: Инициируем выбор файла на клиенте
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
// Создаем описание оповещения, которое выполнится после выбора файла
ОписаниеОповещения = Новый Описание ОписаниеОповещения("ОбработатьВыбранныеФайлы", ЭтаФорма);
ДиалогОткрытияФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ДиалогОткрытияФайла.МножественныйВыбор = Ложь;
ДиалогОткрытияФайла.Заголовок = "Выберите файл Excel";
ДиалогОткрытияФайла.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx";
// Асинхронная функция, которая откроет диалог и поместит файл в хранилище
НачатьПомещениеФайлов(ОписаниеОповещения, , ДиалогОткрытияФайла, Истина, УникальныйИдентификатор);
КонецПроцедуры
// Шаг 2: Обрабатываем результат выбора файла на клиенте и передаем на сервер
&НаКлиенте
Процедура ОбработатьВыбранныеФайлы(ПомещенныеФайлы, ДополнительныеПараметры) Экспорт
Если ПомещенныеФайлы = Неопределено Тогда
Возврат; // Пользователь отменил выбор
КонецЕсли;
// ПомещенныеФайлы - это массив, даже если файл один
ПереданныйФайл = ПомещенныеФайлы[0];
// Вызываем серверную процедуру, передавая адрес хранения и имя файла
ПрочитатьФайлНаСервере(ПереданныйФайл.Хранение, ПереданныйФайл.Имя);
КонецПроцедуры
// Шаг 3: Получаем данные на сервере и работаем с ними
&НаСервере
Процедура ПрочитатьФайлНаСервере(АдресВХранилище, ИмяФайла)
// Получаем двоичные данные из временного хранилища по адресу
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХранилище);
// Создаем временный файл на диске сервера
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДвоичныеДанные.Записать(ИмяВременногоФайла);
// Теперь у нас есть путь к файлу, с которым может работать серверный код
Попытка
Excel = Новый COMОбъект("Excel.Application");
WB = Excel.Workbooks.Open(ИмяВременногоФайла); // Работаем с временным файлом
ЛистЭксель = WB.Worksheets(1);
// ... здесь ваш код обработки файла ...
// Для более сложных задач может быть полезна загрузка файлов Excel и CSV, которая умеет читать все листы
Сообщить("Файл успешно открыт на сервере!");
WB.Close(Ложь); // Закрываем книгу без сохранения
Excel.Quit(); // Закрываем приложение Excel
Исключение
Сообщить(ОписаниеОшибки());
КонецПопытки;
// Не забываем удалить временный файл после использования
УдалитьФайлы(ИмяВременногоФайла);
КонецПроцедуры
Предположим, вы все сделали правильно: передали файл на сервер, получили временный путь и пытаетесь его открыть. Но ошибка "Приложению Microsoft Excel не удается получить доступ к файлу..." все равно появляется! В чем же дело на этот раз?
Проблема кроется в особенностях работы COM-автоматизации приложений Microsoft Office. Сервер 1С (процесс rphost.exe) обычно работает от имени специальной системной учетной записи, например, USR1CV8. У этой учетной записи нет полноценного пользовательского профиля, к которому привыкли интерактивные пользователи. При запуске в неинтерактивном режиме (как это происходит на сервере) приложение Excel пытается получить доступ к некоторым папкам из профиля пользователя, в частности, к папке рабочего стола (Desktop). Если этой папки нет, приложение аварийно завершается, генерируя ошибку доступа к файлу, хотя реальная причина — в окружении.
Решение — создать эту папку вручную на сервере 1С.
Вам необходимо зайти на сервер, где установлен сервер 1С:Предприятия, и создать папку Desktop в системном профиле. Путь зависит от разрядности вашей операционной системы Windows:
C:\Windows\SysWOW64\config\systemprofile\DesktopC:\Windows\System32\config\systemprofile\DesktopПосле создания этой папки (права на нее должны быть у системных служб по умолчанию) перезапускать сервер 1С или службы не требуется. Попробуйте выполнить вашу обработку снова — с большой вероятностью, ошибка исчезнет.
Использование COM-автоматизации на сервере — не самая лучшая практика. У этого подхода есть существенные недостатки, поэтому часто лучше использовать инструменты для универсальной загрузки из Excel, где наличие самого Excel на компьютере не требуется. Основные проблемы COM:
К счастью, платформа 1С предоставляет встроенные средства для чтения файлов Excel без привлечения сторонних приложений. Основной инструмент для этого — объект ТабличныйДокумент, который позволяет реализовать полноценный импорт данных из форматов xls, xlsx и даже ods.
Давайте перепишем наш серверный код с использованием этого объекта. Он становится значительно проще и надежнее.
&НаСервере
Процедура ПрочитатьФайлНаСервере(АдресВХранилище, ИмяФайла)
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХранилище);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
ДвоичныеДанные.Записать(ИмяВременногоФайла);
// Используем встроенный объект 1С
ТабДок = Новый ТабличныйДокумент;
Попытка
// Метод Прочитать() сам распознает формат xls или xlsx
ТабДок.Прочитать(ИмяВременногоФайла);
// Теперь все данные из файла находятся в объекте ТабличныйДокумент.
// Можно обходить ячейки, получать значения и т.д.
// Например, получим значение из ячейки R1C1 (A1)
Область = ТабДок.Область("R1C1");
ЗначениеЯчейки = Область.Текст;
Сообщить("Файл успешно прочитан с помощью ТабличногоДокумента! Значение A1: " + ЗначениеЯчейки);
Исключение
Сообщить("Ошибка при чтении файла: " + ОписаниеОшибки());
КонецПопытки;
УдалитьФайлы(ИмяВременногоФайла);
КонецПроцедуры
Работа с многолистовыми файлами:
Объект ТабличныйДокумент также поддерживает работу с файлами, содержащими несколько листов. Каждый лист Excel загружается как именованная область. Имена областей совпадают с именами листов (с заменой пробелов на символы подчеркивания).
// ... код чтения файла в ТабДок ...
// Перебираем все листы (области) в документе
Для каждого ОбластьЛиста Из ТабДок.Области Цикл
ИмяЛиста = ОбластьЛиста.Имя;
Сообщить("Обрабатываем лист: " + ИмяЛиста);
// Можно получить конкретную ячейку на этом листе
// Например, ячейку B2 (вторая строка, второй столбец)
Значение = ТабДок.Область(2, 2, 2, 2).Текст; // Для первого листа
// Чтобы получить ячейку с конкретного листа, нужно использовать его как область
ЯчейкаНаЛисте = ОбластьЛиста.Область(2, 2);
ЗначениеСЛиста = ЯчейкаНаЛисте.Текст;
КонецЦикла;
Таким образом, использование объекта ТабличныйДокумент не только решает проблему с доступом к файлу на сервере, но и делает ваш код более надежным. Этот способ является предпочтительным.