При разработке обработок выгрузки данных программисты нередко сталкиваются с ситуацией: пользователь запускает сеанс 1С через RDP (удаленный рабочий стол), выбирает для сохранения путь на своем локальном перенаправленном диске или сетевом ресурсе, но при выполнении кода файл не появляется либо платформа возвращает ошибку доступа. При этом интерактивно, вручную через проводник Windows в том же сеансе RDP, файлы на этом диске успешно создаются и изменяются.
Разберем детально, почему возникает такое поведение, как устроено взаимодействие сеансов и какими способами можно правильно организовать выгрузку данных.
Проанализируем исходную процедуру, которая приводит к ошибке:
&НаСервере
Процедура СохранитьРеализацииТоваров(ПутьСохранения)
КолвоДокументов = 0;
ДанныеXML = Новый ЗаписьXML;
ДанныеXML.ОткрытьФайл(ПутьСохранения);
ДанныеXML.ЗаписатьОбъявлениеXML();
ДанныеXML.ЗаписатьНачалоЭлемента("ROOT");
// Запись данных документов
ДанныеXML.ЗаписатьКонецЭлемента();
ДанныеXML.Закрыть();
КонецПроцедуры
На первый взгляд код выглядит корректно. Однако директива компиляции &НаСервере означает, что метод ОткрытьФайл() объекта ЗаписьXML выполняется рабочим процессом сервера 1С — rphost. В этой точке возникает сразу два системных ограничения:
\\tsclient\c или подключенные буквы дисков). Этот виртуальный сетевой ресурс существует исключительно внутри контекста интерактивной сессии данного конкретного пользователя. Служба сервера 1С (usr1cv8) работает в системной нулевой сессии (Session 0) на физическом или виртуальном сервере приложений. Служба сервера в принципе не видит клиентские перенаправленные диски RDP из чужой сессии и не имеет к ним доступа.usr1cv8 пытается обратиться к нему от имени учетной записи, под которой запущен агент сервера. Если это локальный пользователь сервера, во внешнюю сеть обращение пойдет от учетной записи компьютера ДОМЕН\ИмяСервера1С$, у которой, как правило, нет прав на целевую сетевую папку.Таким образом, попытка записать файл на диск RDP-клиента непосредственно из серверного контекста 1С невыполнима на уровне архитектуры операционной системы.
Рассмотрим наиболее надежный и масштабируемый шаблон решения. Он позволяет выполнять тяжелую выборку данных и первичное формирование XML на сервере (где доступны метаданные и данные базы), а само физическое сохранение файла осуществлять в контексте клиентского сеанса RDP.
Разберем реализацию по шагам:
ПолучитьИмяВременногоФайла().ЗаписьXML.ДвоичныеДанные, помещаем его во временное хранилище через метод ПоместитьВоВременноеХранилище() и сразу удаляем временный файл с сервера с помощью УдалитьФайлы().ДвоичныеДанные.Записать() либо переносим через КопироватьФайл().Посмотрим на практический пример реализации:
&НаСервере
Функция СформироватьФайлНаСервере()
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xml");
ДанныеXML = Новый ЗаписьXML;
ДанныеXML.ОткрытьФайл(ИмяВременногоФайла, "UTF-8");
ДанныеXML.ЗаписатьОбъявлениеXML();
ДанныеXML.ЗаписатьНачалоЭлемента("ROOT");
// Формирование структуры и запись элементов
Для Каждого СтрТаб Из Объект.ДокументыРеализации Цикл
Если СтрТаб.ГрузитьРеализ Тогда
ДанныеXML.ЗаписатьНачалоЭлемента("РеализацияТоваровУслуг");
// Заполнение реквизитов документа
ДанныеXML.ЗаписатьКонецЭлемента();
КонецЕсли;
КонецЦикла;
ДанныеXML.ЗаписатьКонецЭлемента();
ДанныеXML.Закрыть();
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайла);
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, УникальныйИдентификатор);
УдалитьФайлы(ИмяВременногоФайла);
Возврат АдресХранилища;
КонецФункции
&НаКлиенте
Процедура СохранитьВыгрузку(ПутьСохранения)
АдресФайла = СформироватьФайлНаСервере();
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресФайла);
ДвоичныеДанные.Записать(ПутьСохранения);
ОчиститьВременноеХранилище(АдресФайла);
КонецПроцедуры
Рассмотрим альтернативный вариант. Объект ЗаписьXML доступен для использования не только на сервере, но и на клиенте (включая тонкий клиент). Если объем выгружаемых документов невелик, мы можем подготовить данные на сервере в виде коллекции (массива структур), вернуть их на клиент и выполнить формирование файла непосредственно в процедуре &НаКлиенте.
Посмотрим на пример выгрузки на клиенте:
&НаСервере
Функция ПолучитьДанныеДляВыгрузки()
ТаблицаДанных = Новый Массив;
Для Каждого СтрТаб Из Объект.ДокументыРеализации Цикл
Если СтрТаб.ГрузитьРеализ Тогда
СтруктураДок = Новый Структура("Номер, Дата", СтрТаб.СсылкаРеализации.Номер, СтрТаб.СсылкаРеализации.Дата);
ТаблицаДанных.Добавить(СтруктураДок);
КонецЕсли;
КонецЦикла;
Возврат ТаблицаДанных;
КонецФункции
&НаКлиенте
Процедура ВыполнитьСохранениеНаКлиенте(ПутьСохранения)
ДанныеВыгрузки = ПолучитьДанныеДляВыгрузки();
ДанныеXML = Новый ЗаписьXML;
ДанныеXML.ОткрытьФайл(ПутьСохранения, "UTF-8");
ДанныеXML.ЗаписатьОбъявлениеXML();
ДанныеXML.ЗаписатьНачалоЭлемента("ROOT");
Для Каждого Элемент Из ДанныеВыгрузки Цикл
ДанныеXML.ЗаписатьНачалоЭлемента("Документ");
ДанныеXML.ЗаписатьАтрибут("Номер", Строка(Элемент.Номер));
ДанныеXML.ЗаписатьКонецЭлемента();
КонецЦикла;
ДанныеXML.ЗаписатьКонецЭлемента();
ДанныеXML.Закрыть();
КонецПроцедуры
В этом случае метод ОткрытьФайл() выполняется внутри сеанса RDP пользователя, которому сопоставлен ресурс, и ошибка записи не возникает.
При сохранении файлов через RDP следует учитывать важные особенности:
\\tsclient может вызывать подвисания интерфейса из-за сетевых таймаутов протокола RDPDR. В таких сценариях рекомендуется сначала сформировать файл в локальный каталог временных файлов клиента с помощью функции КаталогВременныхФайлов(), а затем выполнить быстрое перемещение готового файла через КопироватьФайл() или ПереместитьФайл().