Двоичные данные в 1С 8.3: сохранение на диск, хранилище значения и передача на клиент
Содержание
Чтобы получить файл из двоичных данных в 1С, вызовите у значения типа ДвоичныеДанные метод Записать(ИмяФайла): ДД.Записать("C:\Выгрузка\счет.pdf"). Файл появится на том компьютере, где выполняется код. Если данные лежат в ХранилищеЗначения, сначала извлеките их методом Получить(), а если файл нужен пользователю на его ПК — передайте данные на клиент через временное хранилище.
Главная ловушка не в синтаксисе, а в месте записи. Код на сервере кладёт файл на диск сервера, и пользователь его не видит. Ниже — как преобразовать двоичные данные в файл в каждом режиме, как достать их из хранилища и макета и как не упасть на неверном пути.
Типы данных: что хранится в базе и как это распознать
В 1С 8.3 содержимое файла встречается в трёх видах. От вида зависит первый шаг.
| Где лежат данные | Тип значения | Как получить ДвоичныеДанные |
|---|---|---|
| Реквизит объекта или регистра | ХранилищеЗначения | Реквизит.Получить() |
| Переменная, результат функции | ДвоичныеДанные | уже готово |
| Строка из веб-сервиса, JSON, XML | Строка в Base64 | Base64Значение(Строка) или ПолучитьДвоичныеДанныеИзBase64Строки(Строка) |
| Макет типа «Двоичные данные» | ДвоичныеДанные | ПолучитьМакет("Имя"), ПолучитьОбщийМакет("Имя") |
| Временное хранилище | адрес-строка | ПолучитьИзВременногоХранилища(Адрес) |
ХранилищеЗначения — контейнер. Внутри может быть что угодно: двоичные данные, картинка, таблица значений, структура. Поэтому после Получить() проверьте тип:
Значение = Объект.ФайлХранилище.Получить();
Если ТипЗнч(Значение) <> Тип("ДвоичныеДанные") Тогда
ВызватьИсключение "В хранилище не двоичные данные";
КонецЕсли;
Если внутри Картинка, двоичные данные получают её методом ПолучитьДвоичныеДанные().
💡 Пустое хранилище возвращает Неопределено. Пустые двоичные данные — объект с Размер() = 0. Обе ситуации стоит отсечь до записи, иначе на диске окажется файл нулевого размера.
Запись двоичных данных в файл на диске: базовый алгоритм
Порядок одинаков для любого источника:
- Получите значение типа
ДвоичныеДанные. - Соберите полный путь: каталог + имя + расширение.
- Убедитесь, что каталог существует, при необходимости создайте его.
- Вызовите
Записать()внутриПопытка … Исключение. - Если файл временный — удалите его после использования.
Минимальный рабочий пример для сервера:
Функция СохранитьДвоичныеДанныеВФайл(ДД, Каталог, ИмяФайла) Экспорт
Если ДД = Неопределено Или ДД.Размер() = 0 Тогда
Возврат "";
КонецЕсли;
КаталогФайл = Новый Файл(Каталог);
Если Не КаталогФайл.Существует() Тогда
СоздатьКаталог(Каталог);
КонецЕсли;
ПолноеИмя = Каталог + ПолучитьРазделительПути() + ИмяФайла;
Попытка
ДД.Записать(ПолноеИмя);
Исключение
ЗаписьЖурналаРегистрации("Выгрузка файла", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат "";
КонецПопытки;
Возврат ПолноеИмя;
КонецФункции
ПолучитьРазделительПути() вернёт \ на Windows и / на Linux. Так код не сломается при переезде сервера 1С на другую ОС.
Для промежуточного файла не придумывайте путь вручную. ПолучитьИмяВременногоФайла("pdf") выдаёт уникальное имя в каталоге временных файлов, а УдалитьФайлы(ИмяФайла) убирает его после обработки.
Обратная операция
Прочитать файл обратно в двоичные данные — Новый ДвоичныеДанные(ПолноеИмя). Это удобно для проверки: размер прочитанного должен совпасть с ДД.Размер().
Извлечение файла из ХранилищаЗначения и макета
Реквизит с типом ХранилищеЗначения
Значение хранилища читают на сервере. В управляемой форме такой реквизит на клиент не передаётся, поэтому извлечение пишут в серверной процедуре:
&НаСервере
Функция АдресФайлаНаСервере()
ОбъектСсылка = Объект.Ссылка;
ДД = ОбъектСсылка.ФайлХранилище.Получить();
Возврат ПоместитьВоВременноеХранилище(ДД, УникальныйИдентификатор);
КонецФункции
Обращение Ссылка.Реквизит читает объект из базы целиком. Если нужно одно поле у сотни документов, быстрее запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка КАК Ссылка,
| Т.ФайлХранилище КАК ФайлХранилище
|ИЗ
| Справочник.ДоговорыСкан КАК Т
|ГДЕ
| Т.Ссылка В (&Ссылки)";
Запрос.УстановитьПараметр("Ссылки", МассивСсылок);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ДД = Выборка.ФайлХранилище.Получить();
// запись на диск
КонецЦикла;
Имя справочника и реквизита в примере условные, подставьте свои.
Если хранилище создавали со сжатием
При записи в хранилище часто указывают сжатие: Новый ХранилищеЗначения(ДД, Новый СжатиеДанных(9)). Распаковывать вручную ничего не нужно: Получить() вернёт исходные байты.
Макет «Двоичные данные»
Шаблоны Word, Excel, XML-схемы и DLL внешних компонент разработчики кладут в макеты. Макет объекта получают через ПолучитьМакет("ИмяМакета") в модуле объекта или менеджера, общий — через ПолучитьОбщийМакет("ИмяМакета"). Для макета типа «Двоичные данные» результат уже имеет тип ДвоичныеДанные и сразу пишется на диск:
Шаблон = ПолучитьОбщийМакет("ШаблонДоговора");
ИмяВрем = ПолучитьИмяВременногоФайла("docx");
Шаблон.Записать(ИмяВрем);
Присоединённые файлы БСП
В типовых конфигурациях на БСП файлы хранятся не в реквизите документа, а в отдельных справочниках присоединённых файлов, причём том может быть на диске. Не лезьте в хранилище напрямую: функция РаботаСФайлами.ДвоичныеДанныеФайла(ПрисоединенныйФайл) сама найдёт данные, где бы они ни лежали.
Где появится файл: файловый и клиент-серверный режим
Это главный источник вопросов «код отработал, а файла нет». Метод Записать() пишет на диск того компьютера, где выполняется процедура.
| Режим и контекст | Куда попадёт файл |
|---|---|
Файловая база, код &НаСервере | на компьютер пользователя: серверная часть работает в его процессе |
| Файловая база, опубликованная на веб-сервере | на веб-сервер |
Клиент-сервер, код &НаСервере, фоновое задание | на сервер 1С, в каталог, доступный пользователю службы |
Тонкий клиент, код &НаКлиенте | на компьютер пользователя |
| Веб-клиент | прямой записи на диск нет, только загрузка через браузер |
Поэтому код, отлаженный на файловой базе, после перевода на сервер «теряет» файлы. Они создаются, но в C:\Выгрузка серверной машины.
Как отдать файл пользователю
Правильная схема для управляемых форм: сервер кладёт двоичные данные во временное хранилище, клиент забирает их по адресу.
&НаКлиенте
Процедура СохранитьФайл(Команда)
Адрес = АдресФайлаНаСервере();
ПараметрыДиалога = Новый ПараметрыДиалогаПолученияФайлов;
ПараметрыДиалога.Заголовок = "Сохранить файл";
Оповещение = Новый ОписаниеОповещения("ПослеСохраненияФайла", ЭтотОбъект);
НачатьПолучениеФайлаССервера(Оповещение, Адрес, "Договор.pdf", ПараметрыДиалога);
КонецПроцедуры
&НаКлиенте
Процедура ПослеСохраненияФайла(ПолученныйФайл, ДополнительныеПараметры) Экспорт
Если ПолученныйФайл = Неопределено Тогда
Возврат; // пользователь отменил диалог
КонецЕсли;
Сообщить("Файл сохранён: " + ПолученныйФайл.Имя);
КонецПроцедуры
В обработчик оповещения приходит описание полученного файла: в его свойстве Имя — полный путь, который выбрал пользователь. Если диалог закрыли кнопкой отмены, приходит Неопределено, и эту ветку нужно обработать, иначе обращение к .Имя вызовет ошибку.
Метод работает и в тонком, и в веб-клиенте: в браузере он запускает обычную загрузку. Устаревший ПолучитьФайл() в новых разработках не используйте — он модальный и конфликтует с режимом без модальности.
Открыть файл сразу после сохранения
Часто пользователю нужно не сохранить файл , а просто посмотреть его: открыть скан или печатную форму в PDF. Тогда диалог выбора каталога лишний. Схема такая:
- На сервере поместите данные во временное хранилище.
- В тонком клиенте заберите данные на клиент (
ПолучитьИзВременногоХранилища(Адрес)) и запишите их методомЗаписать()в путь изПолучитьИмяВременногоФайла("pdf")— без диалога. - Откройте файл программой, назначенной в системе, через
НачатьЗапускПриложения()илиЗапуститьПриложение()с полным именем файла.
В веб-клиенте открытие файла на диске пользователя недоступно: браузер сам решает, показать PDF во вкладке или скачать его.
Каждый просмотр оставляет копию во временном каталоге пользователя. Для конфиденциальных документов — договоров, кадровых приказов — удаляйте файл после закрытия или предупреждайте пользователя.
💡 В конфигурациях на БСП сохранение с диалогом выполняет ФайловаяСистемаКлиент.СохранитьФайл(): показывает диалог и учитывает тип клиента.
Двоичные данные можно вернуть с сервера и напрямую, как результат функции, — тип сериализуется. Но для файлов в десятки мегабайт временное хранилище надёжнее: данные не гоняются туда-обратно при каждом серверном вызове формы.
Права на сервере
На сервере 1С файл пишет учётная запись службы, а не пользователь 1С. На Windows это пользователь, под которым запущена служба агента, на Linux — обычно usr1cv8. Нет прав на каталог — получите «Ошибка доступа к файлу». Для обмена с сервером заведите отдельный каталог и выдайте права именно этой учётной записи.
Сетевые пути вида \\Сервер\Обмен из службы часто недоступны: локальная системная учётная запись не имеет прав в сети. Запускайте службу под доменным пользователем или пишите в локальный каталог.
Проверка путей и перехват ошибок записи
Ошибки записи почти всегда связаны с путём. Проверьте его до вызова Записать().
🔍 Что проверять:
- 📁 каталог существует —
Новый Файл(Каталог).Существует(), иначеСоздатьКаталог(); - 🚫 в имени нет запрещённых символов
\ / : * ? " < > |— они приходят из наименований документов и контрагентов; - 📏 длина пути — на Windows без особых настроек система ограничивает полный путь 260 символами;
- 🔒 файл не открыт в Word или Excel — запись поверх открытого файла упадёт;
- 🧩 безопасный режим — во внешних обработках и расширениях с безопасным режимом, а также в облачных сервисах доступ к файловой системе может быть запрещён профилем безопасности.
Очистка имени файла от лишних символов:
Функция БезопасноеИмяФайла(Знач Имя)
Запрещенные = "\/:*?""<>|";
Для Сч = 1 По СтрДлина(Запрещенные) Цикл
Имя = СтрЗаменить(Имя, Сред(Запрещенные, Сч, 1), "_");
КонецЦикла;
Возврат СокрЛП(Имя);
КонецФункции
В строке "\/:*?""<>|" двойная кавычка удвоена — так она экранируется в строковом литерале 1С. В БСП есть готовая функция для того же: ОбщегоНазначенияКлиентСервер.ЗаменитьНедопустимыеСимволыВИмениФайла().
Большие файлы, пакетная выгрузка и оптимизация
Потоки вместо целых двоичных данных
Объект ДвоичныеДанные держит всё содержимое в памяти. Для файлов в сотни мегабайт удобнее потоки: копирование идёт частями.
ПотокЧтения = ДД.ОткрытьПотокДляЧтения();
ПотокЗаписи = Новый ФайловыйПоток(ПолноеИмя, РежимОткрытияФайла.Создать);
ПотокЧтения.КопироватьВ(ПотокЗаписи);
ПотокЗаписи.Закрыть();
ПотокЧтения.Закрыть();
Если источник сам умеет отдавать поток — например, HTTP-ответ, — копируйте из него в файловый поток напрямую, не собирая промежуточные ДвоичныеДанные.
Выгрузка сотни файлов разом
Типичная задача: сохранить сканы всех договоров в папку. Что ускоряет её:
- Один запрос вместо обращения к каждой ссылке.
- Выполнение на сервере или в фоновом задании, а не на клиенте с вызовом сервера на каждый файл.
- Уникальные имена: к наименованию добавьте номер или дату, иначе файлы с одинаковым названием перезапишут друг друга.
- Архив для передачи пользователю: соберите файлы через
ЗаписьZipФайлаи отдайте клиенту один архив.
Пример сборки архива:
ИмяАрхива = ПолучитьИмяВременногоФайла("zip");
Архив = Новый ЗаписьZipФайла(ИмяАрхива);
Для Каждого ИмяФайла Из МассивИменФайлов Цикл
Архив.Добавить(ИмяФайла);
КонецЦикла;
Архив.Записать();
АдресАрхива = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ИмяАрхива), УникальныйИдентификатор);
УдалитьФайлы(ИмяАрхива);
Временные файлы, из которых собирали архив, тоже удалите, иначе каталог временных файлов сервера разрастётся.
Данные из Base64
Веб-сервисы, ЭДО и API маркетплейсов передают файлы строкой Base64. Ошибка новичка — записать такую строку через ЗаписьТекста: получится текстовый файл с буквами вместо PDF. Сначала декодируйте:
ДД = ПолучитьДвоичныеДанныеИзBase64Строки(СтрокаBase64);
ДД.Записать(ПолучитьИмяВременногоФайла("pdf"));
Если строка пришла с префиксом вида data:application/pdf;base64,, отрежьте всё до запятой включительно — иначе декодирование вернёт пустой результат или испорченные данные.
Если вы складываете файлы обратно в хранилище, не храните крупные файлы в реквизитах справочников и документов без нужды: база растёт, а каждое чтение объекта тянет всё содержимое. Для таких данных используют отдельные справочники или хранение в томах на диске.