Файл с диска пользователя на сервер 1С: временное хранилище, код и ошибки
Содержание
Чтобы поместить файл во временное хранилище 1С, на клиенте вызывают НачатьПомещениеФайла() с уникальным идентификатором формы. Платформа передаёт файл на сервер и возвращает адрес вида e1cib/tempstorage/…. На сервере по этому адресу берут ДвоичныеДанные через ПолучитьИзВременногоХранилища(). Если файл уже лежит на сервере, хватает ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(Путь), УникальныйИдентификатор).
Типичная ситуация: бухгалтер просит кнопку «Загрузить выписку из Excel» в обработке. Форма открыта на клиенте, файл лежит на его компьютере, а разбирать его нужно на сервере. Мост между ними и есть временное хранилище. Отдельной платформенной функции с именем «ПоместитьФайлВоВременноеХранилище» нет: задачу решают связкой из двух-трёх методов, и порядок их вызова важен.
Как устроено временное хранилище 1С и что в нём можно хранить
Временное хранилище — область на стороне сервера 1С, привязанная к сеансу пользователя. Каждое значение в нём доступно по строковому адресу. Адрес можно спокойно передавать между клиентом и сервером: это обычная строка, а сами данные по сети лишний раз не гоняются.
Что можно положить:
- 📄
ДвоичныеДанные— содержимое любого файла: xlsx, pdf, xml, картинки, архивы; - 🧾
ТабличныйДокумент,ТаблицаЗначений,Структура,Массив— любые сериализуемые значения; - 🖼
Картинка— например, для показа в поле формы через адрес.
Чего положить нельзя: несериализуемые объекты — ЗаписьXML, открытый ЧтениеТекста, COM-объект, форму.
Сколько живёт значение
Время жизни задаёт второй параметр ПоместитьВоВременноеХранилище():
| Что передано вторым параметром | Сколько живёт значение |
|---|---|
УникальныйИдентификатор формы | До закрытия формы |
| Адрес уже существующего хранилища | Заменяет значение по этому адресу, срок прежний |
| Ничего | Только до конца текущего вызова сервера |
Без идентификатора формы данные исчезнут после возврата с сервера. Это главная причина ситуации «на клиенте получили адрес, а на сервере по нему пусто». В любом случае значение не переживает сеанс пользователя.
Каким методом поместить файл во временное хранилище
Метод зависит от того, где физически лежит файл и в какой версии платформы вы работаете.
| Метод | Где вызывается | Когда подходит |
|---|---|---|
НачатьПомещениеФайла() | Клиент | Один файл с компьютера пользователя, классический асинхронный вариант |
НачатьПомещениеФайлов() | Клиент | Несколько файлов за раз |
ПоместитьФайлНаСерверАсинх() | Клиент | Платформа 8.3.18 и новее: асинхронная модель с Ждать, параметры диалога через ПараметрыДиалогаПомещенияФайлов |
ПоместитьВоВременноеХранилище() | Клиент и сервер | Файл уже прочитан в ДвоичныеДанные или лежит на диске сервера |
ПоместитьФайл() | Клиент | Устаревший синхронный метод; при запрете модальности в конфигурации вызывает ошибку |
Синтаксис НачатьПомещениеФайла
НачатьПомещениеФайла(ОписаниеОповещения, Адрес, НачальноеИмяФайла, Интерактивно, УникальныйИдентификаторФормы);
Что означает каждый параметр:
ОписаниеОповещения— процедура, которую платформа вызовет после передачи файла.Адрес— пусто, если нужен новый адрес. Если передать существующий, файл ляжет по нему.НачальноеИмяФайла— путь, подставленный в диалог, или путь для загрузки без диалога.Интерактивно—Истинаоткрывает окно выбора файла.Ложьгрузит файл по пути без вопросов.УникальныйИдентификаторФормы— почти всегдаУникальныйИдентификатортекущей формы.
Процедура-обработчик получает четыре параметра: Результат (булево — выбран ли файл), Адрес, ВыбранноеИмяФайла и ДополнительныеПараметры. Её обязательно объявляют с Экспорт, иначе оповещение не сработает.
Пример: пользователь выбирает файл, сервер его разбирает
Полный пример для формы обработки: кнопка открывает диалог, файл уходит во временное хранилище, сервер читает его как табличный документ.
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ЗагрузитьФайлЗавершение", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Не Результат Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
КоличествоСтрок = ПрочитатьФайлНаСервере(Адрес, ВыбранноеИмяФайла);
Сообщить(СтрШаблон("Прочитано строк: %1", Формат(КоличествоСтрок, "ЧГ=0")));
КонецПроцедуры
&НаСервере
Функция ПрочитатьФайлНаСервере(Адрес, ИмяФайла)
Данные = ПолучитьИзВременногоХранилища(Адрес);
УдалитьИзВременногоХранилища(Адрес);
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
Данные.Записать(ИмяВременногоФайла);
Таблица = Новый ТабличныйДокумент;
Попытка
Таблица.Прочитать(ИмяВременногоФайла);
Исключение
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(ИмяВременногоФайла);
Возврат Таблица.ВысотаТаблицы;
КонецФункции
Три приёма, на которых держится этот код:
- 🔑 Идентификатор формы в пятом параметре. Без него адрес «протухнет» между клиентским и серверным вызовом.
- 💾 Промежуточный файл на сервере.
ТабличныйДокумент.Прочитать(),ЧтениеXML.ОткрытьФайл()и многие другие объекты принимают путь, а не двоичные данные. Поэтому данные пишут вПолучитьИмяВременногоФайла()и сразу удаляют после чтения — даже если чтение упало. - 🧹 Удаление из хранилища сразу после получения. Данные уже в переменной, держать копию в памяти сервера незачем.
В конфигурациях на БСП то же самое делает ФайловаяСистемаКлиент.ЗагрузитьФайл(). Модуль сам выбирает асинхронный метод и учитывает веб-клиент. Если библиотека есть в конфигурации, начните с неё.
Объект ДвоичныеДанные: прочитать, записать, передать дальше
Почти всё, что вы кладёте во временное хранилище при работе с файлами, — это ДвоичныеДанные. Объект хранит содержимое файла целиком в памяти и не знает ни имени, ни расширения. Имя передавайте рядом: отдельным параметром или в структуре.
Основные операции:
| Задача | Код |
|---|---|
| Прочитать файл с диска | Данные = Новый ДвоичныеДанные(ПутьКФайлу); |
| Записать на диск | Данные.Записать(ПутьКФайлу); |
| Узнать размер в байтах | Данные.Размер() |
| Перевести в строку Base64 | ПолучитьBase64СтрокуИзДвоичныхДанных(Данные) |
| Получить из строки Base64 | ПолучитьДвоичныеДанныеИзBase64Строки(Строка) |
| Читать потоком | Поток = Данные.ОткрытьПотокДляЧтения(); |
Текст и XML можно разобрать без файла на диске
Для текстовых форматов промежуточный файл не нужен. Строку из двоичных данных получают функцией ПолучитьСтрокуИзДвоичныхДанных(Данные, "UTF-8"), а XML читают из строки:
Текст = ПолучитьСтрокуИзДвоичныхДанных(Данные, "UTF-8");
Чтение = Новый ЧтениеXML;
Чтение.УстановитьСтроку(Текст);
Кодировку укажите явно. Выгрузки из старых программ и банковские файлы часто приходят в windows-1251, и без параметра вместо кириллицы получатся «кракозябры».
Как сохранить файл в базе, а не только в хранилище
Временное хранилище живёт до закрытия формы. Если файл нужно сохранить навсегда — например, скан договора у контрагента, — переложите данные в реквизит типа ХранилищеЗначения:
&НаСервере
Процедура СохранитьСканВОбъекте(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
ОбъектСпр = Объект.Ссылка.ПолучитьОбъект();
ОбъектСпр.Скан = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
ОбъектСпр.Записать();
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
Здесь Скан — реквизит справочника с типом ХранилищеЗначения. Обратно данные достают методом Получить(). Хранить в реквизите сам адрес временного хранилища бессмысленно: после закрытия формы он ведёт в пустоту.
Файловый вариант, клиент-сервер и веб-клиент: где на самом деле лежит файл
Код выше одинаково работает во всех вариантах. Различается другое — чья файловая система видна в серверной процедуре.
- 🗂 Файловая база. Серверная часть выполняется на том же компьютере, что и клиент. Поэтому
Новый ДвоичныеДанные("C:\Выгрузка\file.xlsx")в серверной процедуре «случайно» работает. Перенесите базу на сервер 1С — и этот код сломается. - 🖥 Клиент-серверная база. Серверный код выполняется рабочим процессом на сервере 1С. Путь
C:\…в нём — это диск сервера, а не компьютера бухгалтера. Права на чтение — у пользователя Windows, от которого запущена служба сервера 1С, а не у пользователя 1С. - 🌐 Веб-клиент. Интерактивная загрузка через диалог работает без расширения. Загрузка по известному пути (
Интерактивно = Ложь) требует расширения работы с файлами: его подключают черезНачатьПодключениеРасширенияРаботыСФайлами(), а при первом запуске — устанавливают черезНачатьУстановкуРасширенияРаботыСФайлами().
Сетевую папку на сервере указывайте UNC-путём (\\fileserver\obmen\), а не буквой подключённого диска. Буква диска существует только в сеансе конкретного пользователя Windows, а служба 1С её не видит.
Фоновое задание и временное хранилище
Фоновое задание выполняется в отдельном сеансе. Надёжная схема такая: адрес для результата создают заранее в сеансе формы и передают его в задание параметром.
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(ДанныеФайла); // сами ДвоичныеДанные
Параметры.Добавить(АдресРезультата);
ФоновыеЗадания.Выполнить("ОбработкаФайлов.РазобратьФайл", Параметры);
Внутри задания результат кладут вызовом ПоместитьВоВременноеХранилище(Результат, АдресРезультата), а форма потом забирает его по тому же адресу. Сам файл передавайте как ДвоичныеДанные в параметрах: это сериализуемое значение, и оно не зависит от сеанса формы.
Проверки и обработка ошибок при помещении файла в хранилище
Ошибки при работе с файлами всплывают у пользователя в виде непонятного сообщения платформы. Лучше поймать их заранее и показать нормальный текст.
Проверки перед чтением
- Файл на сервере существует.
Новый Файл(Путь).Существует()— до созданияДвоичныеДанные, иначе получите исключение доступа к файлу. - Адрес корректен.
ЭтоАдресВременногоХранилища(Адрес)возвращаетИстина, если строка похожа на адрес хранилища. Защищает от пустой строки и мусора в параметре. - Данные на месте.
ПолучитьИзВременногоХранилища()возвращаетНеопределено, если значение уже удалено или срок его жизни истёк. Проверяйте тип результата до обращения к методам. - Размер разумный.
Данные.Размер()до разбора: файл на 300 МБ лучше отклонить сразу, чем занять память рабочего процесса. - Расширение ожидаемое. Берите его из
ВыбранноеИмяФайлачерезНовый Файл(ИмяФайла).Расширениеи сравнивайте в нижнем регистре.
Шаблон серверной проверки
&НаСервере
Функция ПолучитьДанныеФайла(Адрес, МаксимумМБ = 20)
Если Не ЭтоАдресВременногоХранилища(Адрес) Тогда
ВызватьИсключение "Передан некорректный адрес временного хранилища.";
КонецЕсли;
Данные = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(Данные) <> Тип("ДвоичныеДанные") Тогда
ВызватьИсключение "Файл не найден во временном хранилище. Загрузите его повторно.";
КонецЕсли;
РазмерМБ = Данные.Размер() / 1024 / 1024;
Если РазмерМБ > МаксимумМБ Тогда
ВызватьИсключение СтрШаблон("Файл весит %1 МБ, допустимо не более %2 МБ.",
Формат(РазмерМБ, "ЧДЦ=1"), Формат(МаксимумМБ, "ЧГ=0"));
КонецЕсли;
Возврат Данные;
КонецФункции
В обработчике исключения для журнала регистрации используйте ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()), а пользователю показывайте КраткоеПредставлениеОшибки(). Так бухгалтер видит понятную фразу, а разработчик — стек вызова.
Когда и как очищать временное хранилище
Платформа сама удалит значения при закрытии формы или по окончании сеанса. Но на этот механизм не стоит полагаться в трёх случаях.
- 📦 Большие файлы. Пока форма открыта, данные занимают память сервера. Пользователь загрузил пять архивов по 100 МБ и ушёл на обед — память держится всё это время.
- 🔁 Повторная загрузка в той же форме. Каждый вызов без адреса создаёт новое значение, старые копятся до закрытия формы. Передавайте прежний адрес вторым параметром — новое значение заменит старое.
- 🖥 Долгоживущие формы. Рабочее место, которое открыто весь день, накопит всё, что через него прошло.
Правило простое: забрали данные на сервере — сразу вызовите УдалитьИзВременногоХранилища(Адрес). Если адрес хранится в реквизите формы, после удаления очистите и реквизит, чтобы повторный вызов не обратился к пустому адресу.
Отдельно убирайте временные файлы на диске сервера. ПолучитьИмяВременногоФайла() только придумывает имя, а сам файл платформа автоматически не удаляет. Вызов УдалитьФайлы() нужен в обеих ветках — и после успешного чтения, и в Исключение, как в примере выше.