Двоичные данные в 1С 8.3: сохранение на диск, хранилище значения и передача на клиент

· 9 мин

Содержание

Чтобы получить файл из двоичных данных в 1С, вызовите у значения типа ДвоичныеДанные метод Записать(ИмяФайла): ДД.Записать("C:\Выгрузка\счет.pdf"). Файл появится на том компьютере, где выполняется код. Если данные лежат в ХранилищеЗначения, сначала извлеките их методом Получить(), а если файл нужен пользователю на его ПК — передайте данные на клиент через временное хранилище.

Главная ловушка не в синтаксисе, а в месте записи. Код на сервере кладёт файл на диск сервера, и пользователь его не видит. Ниже — как преобразовать двоичные данные в файл в каждом режиме, как достать их из хранилища и макета и как не упасть на неверном пути.

Типы данных: что хранится в базе и как это распознать

В 1С 8.3 содержимое файла встречается в трёх видах. От вида зависит первый шаг.

Где лежат данныеТип значенияКак получить ДвоичныеДанные
Реквизит объекта или регистраХранилищеЗначенияРеквизит.Получить()
Переменная, результат функцииДвоичныеДанныеуже готово
Строка из веб-сервиса, JSON, XMLСтрока в Base64Base64Значение(Строка) или ПолучитьДвоичныеДанныеИзBase64Строки(Строка)
Макет типа «Двоичные данные»ДвоичныеДанныеПолучитьМакет("Имя"), ПолучитьОбщийМакет("Имя")
Временное хранилищеадрес-строкаПолучитьИзВременногоХранилища(Адрес)

ХранилищеЗначения — контейнер. Внутри может быть что угодно: двоичные данные, картинка, таблица значений, структура. Поэтому после Получить() проверьте тип:

Значение = Объект.ФайлХранилище.Получить();
Если ТипЗнч(Значение) <> Тип("ДвоичныеДанные") Тогда
    ВызватьИсключение "В хранилище не двоичные данные";
КонецЕсли;

Если внутри Картинка, двоичные данные получают её методом ПолучитьДвоичныеДанные().

💡 Пустое хранилище возвращает Неопределено. Пустые двоичные данные — объект с Размер() = 0. Обе ситуации стоит отсечь до записи, иначе на диске окажется файл нулевого размера.

Запись двоичных данных в файл на диске: базовый алгоритм

Порядок одинаков для любого источника:

  1. Получите значение типа ДвоичныеДанные.
  2. Соберите полный путь: каталог + имя + расширение.
  3. Убедитесь, что каталог существует, при необходимости создайте его.
  4. Вызовите Записать() внутри Попытка … Исключение.
  5. Если файл временный — удалите его после использования.

Минимальный рабочий пример для сервера:

Функция СохранитьДвоичныеДанныеВФайл(ДД, Каталог, ИмяФайла) Экспорт

    Если ДД = Неопределено Или ДД.Размер() = 0 Тогда
        Возврат "";
    КонецЕсли;

    КаталогФайл = Новый Файл(Каталог);
    Если Не КаталогФайл.Существует() Тогда
        СоздатьКаталог(Каталог);
    КонецЕсли;

    ПолноеИмя = Каталог + ПолучитьРазделительПути() + ИмяФайла;

    Попытка
        ДД.Записать(ПолноеИмя);
    Исключение
        ЗаписьЖурналаРегистрации("Выгрузка файла", УровеньЖурналаРегистрации.Ошибка, , ,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        Возврат "";
    КонецПопытки;

    Возврат ПолноеИмя;

КонецФункции

ПолучитьРазделительПути() вернёт \ на Windows и / на Linux. Так код не сломается при переезде сервера 1С на другую ОС.

Для промежуточного файла не придумывайте путь вручную. ПолучитьИмяВременногоФайла("pdf") выдаёт уникальное имя в каталоге временных файлов, а УдалитьФайлы(ИмяФайла) убирает его после обработки.

Обратная операция

Прочитать файл обратно в двоичные данные — Новый ДвоичныеДанные(ПолноеИмя). Это удобно для проверки: размер прочитанного должен совпасть с ДД.Размер().

Извлечение файла из ХранилищаЗначения и макета

Реквизит с типом ХранилищеЗначения

Значение хранилища читают на сервере. В управляемой форме такой реквизит на клиент не передаётся, поэтому извлечение пишут в серверной процедуре:

&НаСервере
Функция АдресФайлаНаСервере()
    ОбъектСсылка = Объект.Ссылка;
    ДД = ОбъектСсылка.ФайлХранилище.Получить();
    Возврат ПоместитьВоВременноеХранилище(ДД, УникальныйИдентификатор);
КонецФункции

Обращение Ссылка.Реквизит читает объект из базы целиком. Если нужно одно поле у сотни документов, быстрее запрос:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Ссылка КАК Ссылка,
|    Т.ФайлХранилище КАК ФайлХранилище
|ИЗ
|    Справочник.ДоговорыСкан КАК Т
|ГДЕ
|    Т.Ссылка В (&Ссылки)";
Запрос.УстановитьПараметр("Ссылки", МассивСсылок);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ДД = Выборка.ФайлХранилище.Получить();
    // запись на диск
КонецЦикла;

Имя справочника и реквизита в примере условные, подставьте свои.

Если хранилище создавали со сжатием

При записи в хранилище часто указывают сжатие: Новый ХранилищеЗначения(ДД, Новый СжатиеДанных(9)). Распаковывать вручную ничего не нужно: Получить() вернёт исходные байты.

Макет «Двоичные данные»

Шаблоны Word, Excel, XML-схемы и DLL внешних компонент разработчики кладут в макеты. Макет объекта получают через ПолучитьМакет("ИмяМакета") в модуле объекта или менеджера, общий — через ПолучитьОбщийМакет("ИмяМакета"). Для макета типа «Двоичные данные» результат уже имеет тип ДвоичныеДанные и сразу пишется на диск:

Шаблон = ПолучитьОбщийМакет("ШаблонДоговора");
ИмяВрем = ПолучитьИмяВременногоФайла("docx");
Шаблон.Записать(ИмяВрем);

Присоединённые файлы БСП

В типовых конфигурациях на БСП файлы хранятся не в реквизите документа, а в отдельных справочниках присоединённых файлов, причём том может быть на диске. Не лезьте в хранилище напрямую: функция РаботаСФайлами.ДвоичныеДанныеФайла(ПрисоединенныйФайл) сама найдёт данные, где бы они ни лежали.

Где появится файл: файловый и клиент-серверный режим

Это главный источник вопросов «код отработал, а файла нет». Метод Записать() пишет на диск того компьютера, где выполняется процедура.

Режим и контекстКуда попадёт файл
Файловая база, код &НаСерверена компьютер пользователя: серверная часть работает в его процессе
Файловая база, опубликованная на веб-серверена веб-сервер
Клиент-сервер, код &НаСервере, фоновое заданиена сервер 1С, в каталог, доступный пользователю службы
Тонкий клиент, код &НаКлиентена компьютер пользователя
Веб-клиентпрямой записи на диск нет, только загрузка через браузер

Поэтому код, отлаженный на файловой базе, после перевода на сервер «теряет» файлы. Они создаются, но в C:\Выгрузка серверной машины.

Как отдать файл пользователю

Правильная схема для управляемых форм: сервер кладёт двоичные данные во временное хранилище, клиент забирает их по адресу.

&НаКлиенте
Процедура СохранитьФайл(Команда)
    Адрес = АдресФайлаНаСервере();
    ПараметрыДиалога = Новый ПараметрыДиалогаПолученияФайлов;
    ПараметрыДиалога.Заголовок = "Сохранить файл";
    Оповещение = Новый ОписаниеОповещения("ПослеСохраненияФайла", ЭтотОбъект);
    НачатьПолучениеФайлаССервера(Оповещение, Адрес, "Договор.pdf", ПараметрыДиалога);
КонецПроцедуры

&НаКлиенте
Процедура ПослеСохраненияФайла(ПолученныйФайл, ДополнительныеПараметры) Экспорт
    Если ПолученныйФайл = Неопределено Тогда
        Возврат; // пользователь отменил диалог
    КонецЕсли;
    Сообщить("Файл сохранён: " + ПолученныйФайл.Имя);
КонецПроцедуры

В обработчик оповещения приходит описание полученного файла: в его свойстве Имя — полный путь, который выбрал пользователь. Если диалог закрыли кнопкой отмены, приходит Неопределено, и эту ветку нужно обработать, иначе обращение к .Имя вызовет ошибку.

Метод работает и в тонком, и в веб-клиенте: в браузере он запускает обычную загрузку. Устаревший ПолучитьФайл() в новых разработках не используйте — он модальный и конфликтует с режимом без модальности.

Открыть файл сразу после сохранения

Часто пользователю нужно не сохранить файл , а просто посмотреть его: открыть скан или печатную форму в PDF. Тогда диалог выбора каталога лишний. Схема такая:

  1. На сервере поместите данные во временное хранилище.
  2. В тонком клиенте заберите данные на клиент (ПолучитьИзВременногоХранилища(Адрес)) и запишите их методом Записать() в путь из ПолучитьИмяВременногоФайла("pdf") — без диалога.
  3. Откройте файл программой, назначенной в системе, через НачатьЗапускПриложения() или ЗапуститьПриложение() с полным именем файла.

В веб-клиенте открытие файла на диске пользователя недоступно: браузер сам решает, показать PDF во вкладке или скачать его.

Каждый просмотр оставляет копию во временном каталоге пользователя. Для конфиденциальных документов — договоров, кадровых приказов — удаляйте файл после закрытия или предупреждайте пользователя.

💡 В конфигурациях на БСП сохранение с диалогом выполняет ФайловаяСистемаКлиент.СохранитьФайл(): показывает диалог и учитывает тип клиента.

Двоичные данные можно вернуть с сервера и напрямую, как результат функции, — тип сериализуется. Но для файлов в десятки мегабайт временное хранилище надёжнее: данные не гоняются туда-обратно при каждом серверном вызове формы.

Права на сервере

На сервере 1С файл пишет учётная запись службы, а не пользователь 1С. На Windows это пользователь, под которым запущена служба агента, на Linux — обычно usr1cv8. Нет прав на каталог — получите «Ошибка доступа к файлу». Для обмена с сервером заведите отдельный каталог и выдайте права именно этой учётной записи.

Сетевые пути вида \\Сервер\Обмен из службы часто недоступны: локальная системная учётная запись не имеет прав в сети. Запускайте службу под доменным пользователем или пишите в локальный каталог.

Проверка путей и перехват ошибок записи

Ошибки записи почти всегда связаны с путём. Проверьте его до вызова Записать().

🔍 Что проверять:

  • 📁 каталог существует — Новый Файл(Каталог).Существует(), иначе СоздатьКаталог();
  • 🚫 в имени нет запрещённых символов \ / : * ? " < > | — они приходят из наименований документов и контрагентов;
  • 📏 длина пути — на Windows без особых настроек система ограничивает полный путь 260 символами;
  • 🔒 файл не открыт в Word или Excel — запись поверх открытого файла упадёт;
  • 🧩 безопасный режим — во внешних обработках и расширениях с безопасным режимом, а также в облачных сервисах доступ к файловой системе может быть запрещён профилем безопасности.

Очистка имени файла от лишних символов:

Функция БезопасноеИмяФайла(Знач Имя)
    Запрещенные = "\/:*?""<>|";
    Для Сч = 1 По СтрДлина(Запрещенные) Цикл
        Имя = СтрЗаменить(Имя, Сред(Запрещенные, Сч, 1), "_");
    КонецЦикла;
    Возврат СокрЛП(Имя);
КонецФункции

В строке "\/:*?""<>|" двойная кавычка удвоена — так она экранируется в строковом литерале 1С. В БСП есть готовая функция для того же: ОбщегоНазначенияКлиентСервер.ЗаменитьНедопустимыеСимволыВИмениФайла().

Диагностика: выберите симптом

Большие файлы, пакетная выгрузка и оптимизация

Потоки вместо целых двоичных данных

Объект ДвоичныеДанные держит всё содержимое в памяти. Для файлов в сотни мегабайт удобнее потоки: копирование идёт частями.

ПотокЧтения = ДД.ОткрытьПотокДляЧтения();
ПотокЗаписи = Новый ФайловыйПоток(ПолноеИмя, РежимОткрытияФайла.Создать);
ПотокЧтения.КопироватьВ(ПотокЗаписи);
ПотокЗаписи.Закрыть();
ПотокЧтения.Закрыть();

Если источник сам умеет отдавать поток — например, HTTP-ответ, — копируйте из него в файловый поток напрямую, не собирая промежуточные ДвоичныеДанные.

Выгрузка сотни файлов разом

Типичная задача: сохранить сканы всех договоров в папку. Что ускоряет её:

  1. Один запрос вместо обращения к каждой ссылке.
  2. Выполнение на сервере или в фоновом задании, а не на клиенте с вызовом сервера на каждый файл.
  3. Уникальные имена: к наименованию добавьте номер или дату, иначе файлы с одинаковым названием перезапишут друг друга.
  4. Архив для передачи пользователю: соберите файлы через ЗаписьZipФайла и отдайте клиенту один архив.

Пример сборки архива:

ИмяАрхива = ПолучитьИмяВременногоФайла("zip");
Архив = Новый ЗаписьZipФайла(ИмяАрхива);
Для Каждого ИмяФайла Из МассивИменФайлов Цикл
    Архив.Добавить(ИмяФайла);
КонецЦикла;
Архив.Записать();
АдресАрхива = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ИмяАрхива), УникальныйИдентификатор);
УдалитьФайлы(ИмяАрхива);

Временные файлы, из которых собирали архив, тоже удалите, иначе каталог временных файлов сервера разрастётся.

Данные из Base64

Веб-сервисы, ЭДО и API маркетплейсов передают файлы строкой Base64. Ошибка новичка — записать такую строку через ЗаписьТекста: получится текстовый файл с буквами вместо PDF. Сначала декодируйте:

ДД = ПолучитьДвоичныеДанныеИзBase64Строки(СтрокаBase64);
ДД.Записать(ПолучитьИмяВременногоФайла("pdf"));

Если строка пришла с префиксом вида data:application/pdf;base64,, отрежьте всё до запятой включительно — иначе декодирование вернёт пустой результат или испорченные данные.

Если вы складываете файлы обратно в хранилище, не храните крупные файлы в реквизитах справочников и документов без нужды: база растёт, а каждое чтение объекта тянет всё содержимое. Для таких данных используют отдельные справочники или хранение в томах на диске.

Частые вопросы

Можно ли записать двоичные данные в файл прямо на клиенте?
Да, в тонком и толстом клиенте метод Записать() работает на клиенте, если данные уже там. В веб-клиенте прямой записи нет, используйте НачатьПолучениеФайлаССервера.
Как узнать расширение файла, если в хранилище только байты?
Расширение нигде не хранится вместе с данными. Его берут из соседнего реквизита или определяют по первым байтам: PDF начинается с %PDF, ZIP и DOCX — с PK.
Почему ХранилищеЗначения недоступно в коде формы на клиенте?
Этот тип живёт на сервере. Получите содержимое в серверной процедуре и передайте на клиент двоичные данные или адрес во временном хранилище.
Нужно ли удалять временное хранилище после скачивания?
Если поместить данные с уникальным идентификатором формы, хранилище очистится при закрытии формы. Освободить память раньше можно методом УдалитьИзВременногоХранилища(Адрес).
Чем ДвоичныеДанные отличаются от БуферДвоичныхДанных?
ДвоичныеДанные неизменяемы и подходят для хранения и передачи. БуферДвоичныхДанных позволяет читать и менять отдельные байты, он нужен при разборе форматов вручную.