При разработке интеграционных решений на платформе 1С:Предприятие 8 часто возникает задача выгрузки отчетов во внешние системы через веб-сервисы (Web-services). Сценарий выглядит просто: внешний потребитель вызывает метод веб-сервиса, передает параметры (например, имя сотрудника и период), сервер 1С формирует отчет, сохраняет его во временный файл, кодирует в Base64 и возвращает строку обратно. Однако при переносе работающего кода из внешней обработки или интерактивной формы в модуль веб-сервиса разработчики регулярно сталкиваются с каскадом ошибок: от «Метод объекта не обнаружен» до проблем типизации и прав доступа. В этой статье мы подробно разберем причины возникновения этих ошибок и пошагово исправим их.
Рассмотрим ситуацию: в коде веб-сервиса вызывается экспортный метод объекта отчета (например, СформироватьОтчет()), но система выбрасывает исключение о том, что метод не обнаружен. При этом во внешней обработке в режиме толстого клиента все отрабатывает без нареканий.
Выясним причину этой проблемы. Веб-сервисы 1С всегда выполняются на стороне сервера приложений в неинтерактивном сеансе. В модулях объектов отчетов, особенно в старых конфигурациях или отчетах на базе обычных форм, экспортные процедуры часто оборачиваются в директивы компиляции (инструкции препроцессора). Проанализируем типичный пример такого объявления:
#Если ТолстыйКлиентОбычноеПриложение Тогда
Функция СформироватьОтчет(Результат, ДанныеРасшифровки = Неопределено) Экспорт
// Код формирования отчета
КонецФункции
#КонецЕсли
Для сервера 1С, на котором крутится веб-сервис, код внутри данной директивы просто отсутствует. Сервер игнорирует все, что находится внутри блока #Если ТолстыйКлиентОбычноеПриложение. Чтобы исправить эту ошибку, нам необходимо расширить условия компиляции или полностью вынести метод из клиентских директив.
Разберем по шагам, как правильно скорректировать директивы препроцессора. Нам нужно разрешить компиляцию метода на сервере и при внешнем соединении:
#Если ТолстыйКлиентОбычноеПриложение Или Сервер Или ВнешнееСоединение Тогда
Функция СформироватьОтчет(Результат, ДанныеРасшифровки = Неопределено, ВыводВФормуОтчета = Ложь, ВыводитьПоСтраницам = Ложь) Экспорт
// Код формирования отчета теперь доступен для веб-сервиса
КонецФункции
#КонецЕсли
Если внутри самого метода СформироватьОтчет содержатся вызовы клиентских процедур (например, открытие окон, работа с формой или интерактивное сохранение настроек), их необходимо локализовать с помощью точечных директив препроцессора непосредственно внутри тела метода:
#Если ТолстыйКлиентОбычноеПриложение Тогда
СохранитьНастройкуТабличногоДокумента(Результат); // Клиентский метод
#КонецЕсли
После того как мы сделали метод доступным для сервера, мы можем столкнуться со следующей проблемой: компилятор выдает ошибку о том, что глобальная функция или процедура не определена. Чаще всего это происходит с функциями ВосстановитьЗначение() или СохранитьЗначение().
Эти функции предназначены для работы исключительно в интерактивном режиме в толстом клиенте для сохранения настроек пользователя в локальный кэш. На сервере 1С (и, соответственно, в контексте веб-сервиса) этих функций физически не существует.
Рассмотрим, как решить эту проблему современными средствами платформы. Если вам необходимо сохранять или восстанавливать настройки отчета в серверном коде, используйте системные хранилища настроек, которые стабильно работают в любом контексте выполнения:
// Вместо устаревшего ВосстановитьЗначение("НастройкаОтчета")
Настройки = ХранилищеСистемныхНастроек.Загрузить("Отчет РасчетныеЛистки", "ПользовательскиеНастройки");
// Вместо СохранитьЗначение("НастройкаОтчета", Значение)
ХранилищеСистемныхНастроек.Сохранить("Отчет РасчетныеЛистки", "ПользовательскиеНастройки", Значение);
Если старый код обязательно должен оставаться в отчете для совместимости с интерактивным режимом работы пользователей, просто оберните вызовы старых функций в проверку контекста:
#Если Клиент Тогда
Значение = ВосстановитьЗначение("МойПараметр");
#КонецЕсли
Следующая критическая точка — это передача параметров в веб-сервис из внешних систем. Веб-сервисы работают по протоколам SOAP/REST и передают данные в виде простых типов (обычно строк, чисел или дат) через сериализацию XDTO. Рассмотрим типичный пример ошибочного кода в модуле веб-сервиса:
Функция HelloBaza(username, date)
Отчет = Отчеты.РасчетныеЛисткиОрганизаций.Создать();
// Ошибка: username пришел как Строка, у него нет реквизита "Наименование"
ИмяФайла = СокрЛП(username.Наименование);
// Ошибка: Построитель или Компоновщик ожидает Ссылку, а мы передаем Строку
ТиповыеОтчеты.ДобавитьОтбор(Отчет.КомпоновщикНастроек, "ФизЛицо", username);
КонецФункции
Проанализируем ситуацию: переменная username передается из внешней программы в формате строки (например, "Иванов Иван Иванович" или строкового UUID). Строка — это примитивный тип, у нее нет и не может быть свойства Наименование или Ссылка. Попытка обратиться к свойству примитивного типа вызывает аварийное завершение работы веб-сервиса.
Чтобы исправить эту ошибку, нам необходимо выполнить преобразование типов на стороне 1С перед передачей параметров в построитель отчета или СКД. Разберем по шагам правильный алгоритм получения ссылки:
Посмотрим на пример реализации поиска объекта на сервере:
Функция HelloBaza(username, date)
// Шаг 1. Ищем физическое лицо по переданной строке
ФизЛицоСсылка = Справочники.ФизическиеЛица.НайтиПоНаименованию(username);
Если ФизЛицоСсылка.Пустая() Тогда
// Обработка ситуации, когда объект не найден в базе
ВызватьИсключение "Физическое лицо с наименованием '" + username + "' не найдено в базе данных!";
КонецЕсли;
ОтчетРасчетныеЛистки = Отчеты.РасчетныеЛисткиОрганизаций.Создать();
// Шаг 2. Устанавливаем параметры настроек
ТиповыеОтчеты.УстановитьПараметр(ОтчетРасчетныеЛистки.КомпоновщикНастроек, "НачалоПериода", НачалоМесяца(date));
ТиповыеОтчеты.УстановитьПараметр(ОтчетРасчетныеЛистки.КомпоновщикНастроек, "КонецПериода", КонецМесяца(date));
// Шаг 3. Добавляем отбор, передавая Ссылку, а не Строку
ТиповыеОтчеты.ДобавитьОтбор(ОтчетРасчетныеЛистки.КомпоновщикНастроек, "ФизЛицо", ФизЛицоСсылка);
// ... дальнейшая логика формирования отчета
КонецФункции
При передаче неверного типа данных в отборы отчетов (например, когда в поле отбора по физическому лицу передается строка вместо ссылки) платформа выдает ошибку: «Ошибка при установке значения атрибута контекста (Значение)». Это связано со строгой типизацией платформы 1С.
Построитель отчетов и СКД жестко контролируют типы данных полей. Если поле ФизЛицо имеет тип СправочникСсылка.ФизическиеЛица, то присвоение строкового значения в свойство отбора напрямую вызовет исключение. Всегда предварительно находите ссылку на объект в базе данных (как показано в примере выше) и только затем передавайте ее в отбор.
Обратите внимание на то, как в исходном коде формируется имя временного файла для сохранения отчета:
ИмяФайла = СокрЛП(username.Наименование);
КаталогФайла = КаталогВременныхФайлов();
ТекИмяФайла = КаталогФайла + ИмяФайла + ".xls";
Такой подход таит в себе несколько скрытых угроз при промышленной эксплуатации веб-сервиса:
USR1CV8). У этого пользователя должны быть настроены права на запись в целевую директорию.Рассмотрим правильный стандарт работы с временными файлами на сервере 1С. Для обеспечения уникальности имен файлов и исключения конфликтов при параллельных запросах всегда используйте метод ПолучитьИмяВременногоФайла(). Этот метод генерирует гарантированно уникальное имя файла в системной временной папке сервера, к которой у службы 1С гарантированно есть полные права доступа:
// Платформа сама сгенерирует уникальный путь, например: C:\Windows\Temp\v8_XXXX_XX.xls
ТекИмяФайла = ПолучитьИмяВременногоФайла("xls");
// Записываем табличный документ по уникальному пути
ТабличныйДокументРасчетныйЛисток.Записать(ТекИмяФайла, ТипФайлаТабличногоДокумента.XLS);
// Читаем файл в двоичные данные для последующей кодировки в Base64
ДД_Файла = Новый ДвоичныеДанные(ТекИмяФайла);
СтрокаBase64 = Base64Строка(ДД_Файла);
// Обязательно удаляем временный файл сразу после использования, чтобы не засорять диск сервера
Попытка
УдалитьФайлы(ТекИмяФайла);
Исключение
// Запишем предупреждение в технологический журнал, если файл заблокирован
ЗаписьЖурналаРегистрации("ВебСервис.ПечатьОтчета", УровеньЖурналаРегистрации.Предупреждение, , ,
"Не удалось удалить временный файл: " + ТекИмяФайла);
КонецПопытки;
Сведем все полученные знания в единый, отказоустойчивый шаблон функции веб-сервиса для формирования отчетов. Данный код учитывает контекст выполнения, безопасно работает с файловой системой сервера и корректно преобразует типы данных:
Функция СформироватьОтчетДляВнешнейСистемы(ИмяПользователяСтрока, ДатаПериода) Экспорт
// 1. Приведение типов данных (Поиск ссылки в БД)
ФизЛицоСсылка = Справочники.ФизическиеЛица.НайтиПоНаименованию(ИмяПользователяСтрока);
Если ФизЛицоСсылка.Пустая() Тогда
ВызватьИсключение "Ошибка: Физическое лицо '" + ИмяПользователяСтрока + "' не найдено в базе данных.";
КонецЕсли;
// 2. Инициализация объекта отчета
ОтчетОбъект = Отчеты.РасчетныеЛисткиОрганизаций.Создать();
// 3. Настройка параметров компоновщика (или построителя)
НачалоПериода = НачалоМесяца(ДатаПериода);
КонецПериода = КонецМесяца(ДатаПериода);
ТиповыеОтчеты.УстановитьПараметр(ОтчетОбъект.КомпоновщикНастроек, "НачалоПериода", НачалоПериода);
ТиповыеОтчеты.УстановитьПараметр(ОтчетОбъект.КомпоновщикНастроек, "КонецПериода", КонецПериода);
ТиповыеОтчеты.УстановитьПараметр(ОтчетОбъект.КомпоновщикНастроек, "Группировать", Ложь);
ТиповыеОтчеты.УстановитьПараметр(ОтчетОбъект.КомпоновщикНастроек, "ВидРасчетногоЛистка", "Для сотрудника");
// Передаем строго типизированную ссылку
ТиповыеОтчеты.ДобавитьОтбор(ОтчетОбъект.КомпоновщикНастроек, "ФизЛицо", ФизЛицоСсылка);
// 4. Формирование отчета в табличный документ
ТабличныйДокумент = Новый ТабличныйДокумент;
ОтчетОбъект.СформироватьОтчет(ТабличныйДокумент);
// 5. Безопасная работа с временными файлами на сервере
ПутьКВременномуФайлу = ПолучитьИмяВременногоФайла("xls");
Попытка
ТабличныйДокумент.Записать(ПутьКВременномуФайлу, ТипФайлаТабличногоДокумента.XLS);
// Чтение файла и кодирование в Base64
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКВременномуФайлу);
РезультатBase64 = Base64Строка(ДвоичныеДанныеФайла);
Исключение
// Гарантируем очистку файла при возникновении ошибок записи/чтения
Если ФС.СуществуетФайл(ПутьКВременномуФайлу) Тогда
УдалитьФайлы(ПутьКВременномуФайлу);
КонецЕсли;
ВызватьИсключение ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
// Очистка дискового пространства сервера
УдалитьФайлы(ПутьКВременномуФайлу);
Возврат РезультатBase64;
КонецФункции
Соблюдение этих несложных правил позволит вам создавать надежные и быстрые интеграционные механизмы на базе веб-сервисов 1С, которые будут стабильно работать в условиях многопоточных запросов и высоких нагрузок.