ДвоичныеДанные в 1С 8.3: загрузка, сохранение, потоки и передача файлов

· 10 мин

Содержание

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

Дальше всё строится вокруг этого объекта. Его можно записать обратно в файл методом Записать(), превратить в Base64, отправить в теле HTTP-запроса или сохранить в базе через ХранилищеЗначения. Для больших файлов вместо целого объекта в памяти используют потоки. Ниже — рабочие примеры для каждого случая и ошибки, на которых обычно спотыкаются.

Тип ДвоичныеДанные: что внутри и где он работает

ДвоичныеДанные — это неизменяемый набор байтов. Платформе всё равно, что в нём: PDF, картинка, архив или выгрузка XML. Объект хранит содержимое целиком в оперативной памяти того процесса, где его создали.

Свойства, которые важно помнить:

  • 📦 Неизменяемость. Поменять отдельный байт внутри нельзя. Для правки содержимого есть БуферДвоичныхДанных.
  • 🔁 Сериализуемость. Объект можно передать между клиентом и сервером и положить во временное хранилище. Но тяжёлый файл в параметре серверного вызова гоняет трафик туда и обратно, поэтому для передачи используют адрес во временном хранилище.
  • 📏 Размер. Метод Размер() возвращает число байтов. Если нужно узнать размер файла без чтения, используйте Новый Файл(Путь).Размер().
  • 🖥️ Файловая система. Конструктор с путём читает файл с диска того компьютера, где выполняется код. На сервере это диск сервера 1С, а не пользователя.

Код на сервере не видит диск пользователя. Путь C:\Users\Бухгалтер\Desktop\счёт.pdf, выбранный на клиенте, на сервере не существует. Отсюда половина вопросов на форумах.

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

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

Выбор способа зависит от того, где лежит файл и кто его выбирает.

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

Способ 1. Файл на стороне сервера

Подходит для регламентных заданий, обменов через общую папку, загрузки прайсов по расписанию.

&НаСервере
Функция ПрочитатьФайлНаСервере(ИмяФайла)

    Файл = Новый Файл(ИмяФайла);
    Если Не Файл.Существует() Тогда
        ВызватьИсключение СтрШаблон("Файл %1 не найден на сервере", ИмяФайла);
    КонецЕсли;

    ДанныеФайла = Новый ДвоичныеДанные(ИмяФайла);
    Возврат ДанныеФайла;

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

В клиент-серверном варианте код работает от имени пользователя, под которым запущена служба сервера 1С (по умолчанию USR1CV8). Если у этой учётной записи нет прав на сетевую папку, чтение упадёт, хотя у вас в проводнике файл открывается.

Способ 2. Файл выбирает пользователь

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

&НаКлиенте
Процедура ЗагрузитьФайл(Команда)

    Оповещение = Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтотОбъект);
    НачатьПомещениеФайла(Оповещение, "", "", Истина, УникальныйИдентификатор);

КонецПроцедуры

&НаКлиенте
Процедура ПослеПомещенияФайла(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт

    Если Не Результат Тогда
        Возврат; // пользователь закрыл диалог
    КонецЕсли;

    ОбработатьФайлНаСервере(Адрес, ВыбранноеИмяФайла);

КонецПроцедуры

&НаСервере
Процедура ОбработатьФайлНаСервере(Адрес, ИмяФайла)

    ДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
    УдалитьИзВременногоХранилища(Адрес);

    КороткоеИмя = Новый Файл(ИмяФайла).Имя;
    // дальше работаем с ДанныеФайла как с ДвоичныеДанные

КонецПроцедуры

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

Способ 3. Через БСП

В типовых конфигурациях на Библиотеке стандартных подсистем (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) удобнее вызвать ФайловаяСистемаКлиент.ЗагрузитьФайл. Процедура сама учитывает тип клиента, расширение работы с файлами и диалог, а в обработчик возвращает адрес во временном хранилище.

Как сохранить двоичные данные обратно в файл

Здесь тоже два сценария: записать файл на диск сервера или отдать его пользователю.

Запись на сервере

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

// ... передали файл внешней программе, заархивировали и т.д.

УдалитьФайлы(ИмяВременного);

ПолучитьИмяВременногоФайла возвращает уникальное имя в каталоге временных файлов, но сам файл не создаёт и не удаляет. Удаление — ваша забота, иначе каталог временных файлов сервера будет расти.

Выгрузка пользователю

Сервер кладёт данные во временное хранилище, клиент забирает их с диалогом сохранения:

&НаСервере
Функция ПодготовитьФайлНаСервере()

    ДанныеФайла = СформироватьОтчетВPDF(); // ваша функция
    Возврат ПоместитьВоВременноеХранилище(ДанныеФайла, УникальныйИдентификатор);

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

&НаКлиенте
Процедура СохранитьФайл(Команда)

    Адрес = ПодготовитьФайлНаСервере();

    ПередаваемыеФайлы = Новый Массив;
    ПередаваемыеФайлы.Добавить(Новый ОписаниеПередаваемогоФайла("Отчет.pdf", Адрес));

    Оповещение = Новый ОписаниеОповещения("ПослеСохраненияФайла", ЭтотОбъект);
    НачатьПолучениеФайлов(Оповещение, ПередаваемыеФайлы, , Истина);

КонецПроцедуры

&НаКлиенте
Процедура ПослеСохраненияФайла(ПолученныеФайлы, ДополнительныеПараметры) Экспорт

    Если ПолученныеФайлы = Неопределено Тогда
        Возврат; // пользователь отменил сохранение
    КонецЕсли;

КонецПроцедуры

В конфигурациях на БСП то же самое делает ФайловаяСистемаКлиент.СохранитьФайл: передаёте адрес и имя, остальное библиотека берёт на себя.

💡 Если нужно показать картинку в поле формы, сохранять её на диск не нужно. Адрес из ПоместитьВоВременноеХранилище подставляется в реквизит формы, связанный с полем картинки, и платформа выводит изображение сама.

Потоки вместо файла целиком в памяти

Новый ДвоичныеДанные(Путь) читает файл полностью. Для файла на 5 МБ это нормально. Для архива на 2 ГБ рабочий процесс получит такой же объём в памяти, а если таких вызовов несколько параллельно, сервер начнёт задыхаться.

Потоки читают и пишут данные порциями. Основные объекты:

  • 📂 ФайловыеПотоки.ОткрытьДляЧтения(Путь) и ФайловыеПотоки.Создать(Путь) — поток к файлу на диске.
  • 🧠 ПотокВПамяти — поток в оперативной памяти, из него получают двоичные данные методом ЗакрытьИПолучитьДвоичныеДанные().
  • 🔓 ДанныеФайла.ОткрытьПотокДляЧтения() — поток поверх уже имеющихся двоичных данных.
  • 📖 ЧтениеДанных и ЗаписьДанных — чтение и запись порциями, байтами, числами и строками.

Копирование через поток

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

ПотокИсточник = ДанныеФайла.ОткрытьПотокДляЧтения();
ПотокПриемник = ФайловыеПотоки.Создать(ИмяФайла);

ПотокИсточник.КопироватьВ(ПотокПриемник);

ПотокПриемник.Закрыть();
ПотокИсточник.Закрыть();

Чтение порциями

Пример считает контрольную сумму файла любого размера, держа в памяти не больше 1 МБ:

Поток = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
Чтение = Новый ЧтениеДанных(Поток);
Хеш = Новый ХешированиеДанных(ХешФункция.SHA256);

РазмерПорции = 1024 * 1024;
Пока Истина Цикл
    Порция = Чтение.Прочитать(РазмерПорции);
    Если Порция.Размер = 0 Тогда
        Прервать;
    КонецЕсли;
    Хеш.Добавить(Порция.ПолучитьДвоичныеДанные());
КонецЦикла;

Поток.Закрыть();
КонтрольнаяСумма = ПолучитьHexСтрокуИзДвоичныхДанных(Хеш.ХешСумма);

Для обратной задачи есть готовые функции: РазделитьДвоичныеДанные(Данные, РазмерЧасти) режет данные на массив частей, СоединитьДвоичныеДанные(МассивЧастей) собирает обратно. Это удобно, когда сервис принимает файл кусками, например по 10 МБ.

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

Base64 и HTTP: как передать двоичные данные во внешний сервис

Внешние системы принимают файл одним из трёх способов. От этого зависит, что делать с двоичными данными.

Что ждёт сервисЧто делаем в 1С
Сырые байты в теле запросаHTTPЗапрос.УстановитьТелоИзДвоичныхДанных(ДанныеФайла)
Строку Base64 внутри JSON или XMLПолучитьBase64СтрокуИзДвоичныхДанных(ДанныеФайла)
Форму multipart/form-dataСобираем тело из частей через СоединитьДвоичныеДанные

Кодирование в строку и обратно

  • ПолучитьBase64СтрокуИзДвоичныхДанных и ПолучитьДвоичныеДанныеИзBase64Строки — Base64 в обе стороны. Старые аналоги — Base64Строка и Base64Значение.
  • ПолучитьHexСтрокуИзДвоичныхДанных — шестнадцатеричная строка, удобна для хешей и отладки.
  • ПолучитьСтрокуИзДвоичныхДанных(Данные, КодировкаТекста.UTF8) — текст из байтов, если в файле лежит текст.
  • ПолучитьДвоичныеДанныеИзСтроки(Текст, КодировкаТекста.UTF8, Ложь) — байты из текста. Третий параметр отвечает за BOM в начале.

💡 Base64Строка разбивает результат переносами строк. Многие JSON-API такую строку не принимают. Перед вставкой в JSON удалите Символы.ВК и Символы.ПС через СтрЗаменить — с любой из функций, лишним это не будет.

Отправка файла формой multipart/form-data

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

Граница = "----1s83" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");
ПС = Символы.ВК + Символы.ПС;

Начало = "--" + Граница + ПС
    + "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """" + ПС
    + "Content-Type: application/octet-stream" + ПС + ПС;
Конец = ПС + "--" + Граница + "--" + ПС;

Части = Новый Массив;
Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Начало, КодировкаТекста.UTF8, Ложь));
Части.Добавить(ДанныеФайла);
Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Конец, КодировкаТекста.UTF8, Ложь));
Тело = СоединитьДвоичныеДанные(Части);

Соединение = Новый HTTPСоединение("api.example.ru", 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/upload");
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);
Запрос.УстановитьТелоИзДвоичныхДанных(Тело);

Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Если Ответ.КодСостояния <> 200 Тогда
    ВызватьИсключение СтрШаблон("Сервис вернул код %1", Ответ.КодСостояния);
КонецЕсли;

BOM здесь отключён намеренно: три лишних байта перед первой границей ломают разбор формы на стороне сервиса.

Скачивание файла

Обратная операция — одна строка после запроса: ДанныеФайла = Ответ.ПолучитьТелоКакДвоичныеДанные(). Для очень больших файлов метод Получить у HTTPСоединение принимает вторым параметром имя выходного файла — тогда ответ сразу пишется на диск, минуя память.

Где хранить файл внутри базы 1С

Реквизит типа ДвоичныеДанные в конфигураторе создать нельзя. Для хранения файла в базе используют тип ХранилищеЗначения:

// запись
Объект.ДанныеФайла = Новый ХранилищеЗначения(ДанныеФайла, Новый СжатиеДанных(9));

// чтение
ДанныеФайла = Объект.ДанныеФайла.Получить();

СжатиеДанных(9) — максимальная степень сжатия. Для текстов, XML и выгрузок она уменьшает объём в разы. JPEG, PDF, ZIP и DOCX уже сжаты: процессор потратится, а размер почти не изменится.

Где разместить хранилище — вопрос производительности:

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

Для типовых конфигураций правильный путь — механизм присоединённых файлов БСП, а не свой реквизит. Он сам решает, класть файл в базу или в том на диске, по настройкам администратора.

При хранении в томах резервная копия базы (.dt или бэкап СУБД) не содержит самих файлов. Восстановив базу без копии томов, вы получите ссылки на файлы, которых нет.

Частые ошибки при работе с двоичными данными

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

Ещё две ошибки, которые не выдают сообщений, но портят данные:

  • 🧩 Имя файла с кириллицей в multipart. Часть сервисов ждёт имя в отдельном параметре filename* с кодированием. Если на стороне сервиса имя превращается в «кракозябры», проверьте документацию API.
  • 🗑️ Временные файлы без удаления. Каждый ПолучитьИмяВременногоФайла без последующего УдалитьФайлы оставляет мусор в каталоге временных файлов сервера. За месяц регламентных заданий там набираются гигабайты.

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

Чем ДвоичныеДанные отличаются от ХранилищеЗначения?
ДвоичныеДанные — сами байты файла в памяти. ХранилищеЗначения — обёртка для сохранения любого значения в реквизите базы, с возможностью сжатия. Чтобы положить файл в базу, двоичные данные заворачивают в хранилище.
Можно ли изменить несколько байтов в ДвоичныеДанные?
Напрямую нет, объект неизменяемый. Получите БуферДвоичныхДанных через ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных, внесите изменения и преобразуйте обратно функцией ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных.
Как проверить, что два файла одинаковые?
Сравните хеши: ХешированиеДанных с функцией SHA256, метод Добавить для двоичных данных или ДобавитьФайл для файла на диске. Совпали хеши — совпало содержимое.
Как прочитать текстовый файл, если он пришёл как двоичные данные?
Функцией ПолучитьСтрокуИзДвоичныхДанных с правильной кодировкой. Для выгрузок из старых программ часто нужна windows-1251, для современных — UTF-8.
Есть ли ограничение на размер ДвоичныеДанные?
Жёсткого лимита в виде числа платформа не объявляет, всё упирается в память процесса. Файлы в сотни мегабайт и больше обрабатывайте потоками, а не целиком.