Двоичные данные 1С — значение типа ДвоичныеДанные: байты файла, картинки, PDF, архива или тела HTTP-ответа. Ниже — рабочие примеры для 1С 8.3: как получить двоичные данные из файла и строки, записать их на диск, перевести в Base64, посчитать хеш SHA256 или MD5, сохранить в реквизит и передать с клиента на сервер.
Объект ДвоичныеДанные неизменяемый: байт внутри не поменять, можно только получить новый объект. Конструктор читает файл целиком, поэтому файлы на сотни мегабайт обрабатывают потоками. Потоки (ПотокВПамяти, ФайловыйПоток, ЧтениеДанных, ЗаписьДанных) появились в 8.3.9, а функции преобразования строк, Base64 и Hex, склейки и разбиения — в 8.3.10, поэтому примеры рассчитаны на 8.3.10 и новее.
Файл читают конструктором Новый ДвоичныеДанные(ПолноеИмяФайла), сохраняют методом Записать(ПолноеИмяФайла). Путь относится к компьютеру, где выполняется код: в серверной процедуре это диск сервера 1С.
// Общий модуль ФайлыСервер (флажок «Сервер»)
Функция ПрочитатьФайл(ПолноеИмяФайла, МаксимумМБ = 20) Экспорт
Файл = Новый Файл(ПолноеИмяФайла);
Если Не Файл.Существует() Тогда
ВызватьИсключение СтрШаблон("Файл не найден на сервере: %1", ПолноеИмяФайла);
КонецЕсли;
Если Файл.Размер() > МаксимумМБ * 1024 * 1024 Тогда
ВызватьИсключение СтрШаблон("Файл больше %1 МБ: %2", МаксимумМБ, ПолноеИмяФайла);
КонецЕсли;
Возврат Новый ДвоичныеДанные(ПолноеИмяФайла);
КонецФункции
Процедура ЗаписатьФайл(Данные, ПолноеИмяФайла) Экспорт
Попытка
Данные.Записать(ПолноеИмяФайла);
Исключение
ВызватьИсключение СтрШаблон("Не удалось записать %1: %2", ПолноеИмяФайла, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
// Печатная форма в PDF и сразу в ДвоичныеДанные
Функция ТабличныйДокументВPDF(ТабличныйДокумент) Экспорт
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
Попытка
ТабличныйДокумент.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
Данные = Новый ДвоичныеДанные(ИмяВременногоФайла);
Исключение
ТекстОшибки = ОписаниеОшибки();
Если Новый Файл(ИмяВременногоФайла).Существует() Тогда
УдалитьФайлы(ИмяВременногоФайла);
КонецЕсли;
ВызватьИсключение ТекстОшибки;
КонецПопытки;
УдалитьФайлы(ИмяВременногоФайла);
Возврат Данные;
КонецФункции
Файл.Размер() проверяет размер до чтения: в память не попадёт гигабайтный архив, который случайно положили в папку обмена.ОписаниеОшибки() в тексте исключения сохраняет причину: нет каталога, нет прав, файл занят другим процессом.ПолучитьИмяВременногоФайла("pdf") даёт уникальное имя в каталоге временных файлов. Файл удаляется в обеих ветках, иначе каталог Temp на сервере со временем забивается.Кроме файла, двоичные данные возвращают и другие объекты платформы:
| Откуда | Как получить ДвоичныеДанные |
|---|---|
| Файл пользователя | НачатьПомещениеФайла на клиенте, ПолучитьИзВременногоХранилища(Адрес) на сервере |
| Строка | ПолучитьДвоичныеДанныеИзСтроки(Строка, Кодировка) |
| Строка Base64 | ПолучитьДвоичныеДанныеИзBase64Строки(Строка) |
| Макет типа «Двоичные данные» | ПолучитьМакет("Имя") или ПолучитьОбщийМакет("Имя") |
| Ответ HTTP-сервиса | Ответ.ПолучитьТелоКакДвоичныеДанные() |
| Картинка | Картинка.ПолучитьДвоичныеДанные() |
| Реквизит ХранилищеЗначения | Объект.Реквизит.Получить() |
| Поток в памяти | Поток.ЗакрытьИПолучитьДвоичныеДанные() |
ПолучитьДвоичныеДанныеИзСтроки(Строка, Кодировка, ИспользоватьBOM) превращает текст в байты, ПолучитьСтрокуИзДвоичныхДанных(Данные, Кодировка) — обратно. Кодировку передают строкой ("UTF-8", "windows-1251") или значением КодировкаТекста.
&НаСервереБезКонтекста
Процедура ПоказатьКодировки()
Текст = "Акт 15";
ДанныеUTF8 = ПолучитьДвоичныеДанныеИзСтроки(Текст, "UTF-8", Ложь);
Данные1251 = ПолучитьДвоичныеДанныеИзСтроки(Текст, "windows-1251");
ДанныеСBOM = ПолучитьДвоичныеДанныеИзСтроки(Текст, "UTF-8", Истина);
Сообщить("UTF-8: " + ДанныеUTF8.Размер()); // 9: буква кириллицы занимает 2 байта
Сообщить("windows-1251: " + Данные1251.Размер()); // 6: байт на символ
Сообщить("UTF-8 с BOM: " + ДанныеСBOM.Размер()); // 12: плюс метка EF BB BF
// Обратно — в той кодировке, в которой данные записаны
Сообщить(ПолучитьСтрокуИзДвоичныхДанных(Данные1251, "windows-1251")); // Акт 15
КонецПроцедуры
&НаСервереБезКонтекста
Функция ТекстФайлаВ1251(ПолноеИмяФайла)
Данные = Новый ДвоичныеДанные(ПолноеИмяФайла);
Возврат ПолучитьСтрокуИзДвоичныхДанных(Данные, "windows-1251");
КонецФункции
&НаСервереБезКонтекста
Функция CSVДляExcel(СтрокиCSV)
// Без BOM Excel открывает UTF-8 как ANSI, и кириллица превращается в мусор
Текст = СтрСоединить(СтрокиCSV, Символы.ВК + Символы.ПС);
Возврат ПолучитьДвоичныеДанныеИзСтроки(Текст, "UTF-8", Истина);
КонецФункции
Истина в третьем параметре добавляет BOM — три байта EF BB BF. Excel по этой метке правильно открывает CSV, а для JSON, подписи и хеша это лишние байты: сервис отвергает запрос или хеш не совпадает. Поэтому Ложь лучше указывать явно.Base64 нужен, когда файл передают внутри текста: в JSON, XML, теле письма. Старые Base64Строка и Base64Значение по-прежнему работают, в новом коде используйте ПолучитьBase64СтрокуИзДвоичныхДанных и ПолучитьДвоичныеДанныеИзBase64Строки.
// Общий модуль ДвоичныеДанныеСервер (флажок «Сервер»)
Функция ВBase64(Данные) Экспорт
Строка64 = ПолучитьBase64СтрокуИзДвоичныхДанных(Данные);
// В JSON и заголовках HTTP переносы строк не нужны
Строка64 = СтрЗаменить(Строка64, Символы.ВК, "");
Возврат СтрЗаменить(Строка64, Символы.ПС, "");
КонецФункции
Функция ИзBase64(Строка64) Экспорт
Данные = ПолучитьДвоичныеДанныеИзBase64Строки(Строка64);
Если Данные = Неопределено Или Данные.Размер() = 0 Тогда
ВызватьИсключение "Пустая или некорректная строка Base64";
КонецЕсли;
Возврат Данные;
КонецФункции
// Base64URL для JWT и параметров в адресе
Функция ВBase64URL(Данные) Экспорт
Строка64 = ВBase64(Данные);
Строка64 = СтрЗаменить(Строка64, "+", "-");
Строка64 = СтрЗаменить(Строка64, "/", "_");
Возврат СтрЗаменить(Строка64, "=", "");
КонецФункции
// Файл внутри JSON для внешнего сервиса
Функция JSONСФайлом(ИмяФайла, Данные) Экспорт
Тело = Новый Структура;
Тело.Вставить("fileName", ИмяФайла);
Тело.Вставить("content", ВBase64(Данные));
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Тело);
Возврат Запись.Закрыть();
КонецФункции
ВBase64 их удаляет.ВBase64URL меняет + и / на - и _ и убирает =: такой вариант требуют JWT и параметры в адресе.Хеш считает объект ХешированиеДанных: его создают с нужной ХешФункция (SHA256, SHA1, MD5, CRC32), передают данные методом Добавить и читают свойство ХешСумма. Добавить можно вызывать много раз — так хешируют файл по частям.
// Общий модуль ДвоичныеДанныеСервер
// Хеш в виде hex-строки в нижнем регистре, как у sha256sum
Функция ХешСтрокой(Данные, ВидХеша = Неопределено) Экспорт
Если ВидХеша = Неопределено Тогда
ВидХеша = ХешФункция.SHA256;
КонецЕсли;
Хеширование = Новый ХешированиеДанных(ВидХеша);
Хеширование.Добавить(Данные);
// Для SHA256, SHA1 и MD5 ХешСумма имеет тип ДвоичныеДанные, для CRC32 — Число
Возврат НРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хеширование.ХешСумма));
КонецФункции
// ХешСтрокиUTF8("abc") = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
Функция ХешСтрокиUTF8(Текст) Экспорт
Возврат ХешСтрокой(ПолучитьДвоичныеДанныеИзСтроки(Текст, "UTF-8", Ложь));
КонецФункции
Функция ФайлыОдинаковые(Данные1, Данные2) Экспорт
Если Данные1.Размер() <> Данные2.Размер() Тогда
Возврат Ложь; // разный размер: хеш можно не считать
КонецЕсли;
Возврат ХешСтрокой(Данные1) = ХешСтрокой(Данные2);
КонецФункции
Функция ОписаниеФайла(Данные) Экспорт
РазмерМБ = Окр(Данные.Размер() / 1024 / 1024, 2);
Возврат СтрШаблон("%1 МБ, SHA256: %2, MD5: %3", РазмерМБ,
ХешСтрокой(Данные), ХешСтрокой(Данные, ХешФункция.MD5));
КонецФункции
ПолучитьHexСтрокуИзДвоичныхДанных переводит хеш в hex, НРег приводит его к виду, как у sha256sum и большинства API.Размер() возвращает число байт. Разный размер сразу означает разные файлы, одинаковый — повод сравнить хеши.Серверный код не видит диск пользователя. Поэтому файл сначала помещают во временное хранилище, на сервере забирают по адресу и сохраняют в реквизит типа ХранилищеЗначения: типа ДвоичныеДанные у реквизитов объектов нет.
// Модуль формы элемента справочника «Вложения».
// Реквизиты справочника: ДанныеФайла (ХранилищеЗначения), ИмяФайла (Строка), РазмерФайла (Число).
// Реквизит формы: АдресФайла (Строка). Команда формы: ВыбратьФайл.
&НаКлиенте
Процедура ВыбратьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ВыбратьФайлЗавершение", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ВыбратьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Не Результат Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
АдресФайла = Адрес;
Объект.ИмяФайла = Новый Файл(ВыбранноеИмяФайла).Имя;
Модифицированность = Истина;
КонецПроцедуры
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если Не ЭтоАдресВременногоХранилища(АдресФайла) Тогда
Возврат; // файл не выбирали: ДанныеФайла в базе не меняются
КонецЕсли;
Данные = ПолучитьИзВременногоХранилища(АдресФайла);
Если ТипЗнч(Данные) <> Тип("ДвоичныеДанные") Тогда
Сообщить("Файл не получен, выберите его ещё раз.");
Отказ = Истина;
Возврат;
КонецЕсли;
Если Данные.Размер() > 50 * 1024 * 1024 Тогда
Сообщить("Файл больше 50 МБ.");
Отказ = Истина;
Возврат;
КонецЕсли;
ТекущийОбъект.ДанныеФайла = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
ТекущийОбъект.РазмерФайла = Данные.Размер();
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
Если ЭтоАдресВременногоХранилища(АдресФайла) Тогда
УдалитьИзВременногоХранилища(АдресФайла);
АдресФайла = "";
КонецЕсли;
КонецПроцедуры
НачатьПомещениеФайла с Интерактивно = Истина открывает диалог выбора и передаёт файл на сервер, в том числе из веб-клиента. Идентификатор формы привязывает данные к форме: форму закрыли — хранилище очищено.ХранилищеЗначения в данные формы не попадают, поэтому значение записывают в ТекущийОбъект в ПередЗаписьюНаСервере.Новый СжатиеДанных(9) сжимает данные: текст и XML уменьшаются в разы, JPG и ZIP почти не меняются.ПослеЗаписиНаСервере: если запись сорвётся, пользователь нажмёт «Записать» ещё раз без повторного выбора файла.Читают вложение запросом: он выбирает одно поле, а обращение Ссылка.ДанныеФайла через точку считывает весь объект.
// Общий модуль ВложенияСервер (флажки «Сервер» и «Вызов сервера»)
Функция ДанныеВложения(Ссылка) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Вложения.ДанныеФайла КАК ДанныеФайла
|ИЗ
| Справочник.Вложения КАК Вложения
|ГДЕ
| Вложения.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат Неопределено;
КонецЕсли;
Данные = Выборка.ДанныеФайла.Получить();
Возврат ?(ТипЗнч(Данные) = Тип("ДвоичныеДанные"), Данные, Неопределено);
КонецФункции
// Адрес для скачивания на клиенте: живёт, пока открыта форма
Функция АдресВложения(Ссылка, УникальныйИдентификаторФормы) Экспорт
Данные = ДанныеВложения(Ссылка);
Если Данные = Неопределено Тогда
Возврат "";
КонецЕсли;
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификаторФормы);
КонецФункции
Получить() возвращает то, что положили в хранилище, поэтому тип проверяется явно. АдресВложения готовит файл к скачиванию: адрес уходит на клиент, где пользователь выбирает, куда сохранить файл.
РазделитьДвоичныеДанные(Данные, РазмерЧасти) возвращает массив частей, СоединитьДвоичныеДанные(Массив) собирает их обратно. Так режут файл под лимит почты или API и собирают тело multipart-запроса.
// Общий модуль ДвоичныеДанныеСервер
// Режет данные на тома: backup.zip.001, backup.zip.002 и так далее
Функция РазрезатьНаТома(Данные, ПолноеИмяФайла, РазмерТомаМБ = 10) Экспорт
ИменаТомов = Новый Массив;
Тома = РазделитьДвоичныеДанные(Данные, РазмерТомаМБ * 1024 * 1024);
Для Индекс = 0 По Тома.ВГраница() Цикл
ИмяТома = ПолноеИмяФайла + "." + Формат(Индекс + 1, "ЧЦ=3; ЧВН=");
Тома[Индекс].Записать(ИмяТома);
ИменаТомов.Добавить(ИмяТома);
КонецЦикла;
Возврат ИменаТомов;
КонецФункции
Функция СклеитьТома(ИменаТомов) Экспорт
Части = Новый Массив;
Для Каждого ИмяТома Из ИменаТомов Цикл
Части.Добавить(Новый ДвоичныеДанные(ИмяТома));
КонецЦикла;
Возврат СоединитьДвоичныеДанные(Части);
КонецФункции
// Тело multipart/form-data: заголовки части, файл, закрывающая граница
Функция ТелоMultipart(Данные, ИмяФайла, Граница) Экспорт
ПереводСтроки = Символы.ВК + Символы.ПС;
Начало = "--" + Граница + ПереводСтроки
+ "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайла + """" + ПереводСтроки
+ "Content-Type: application/octet-stream" + ПереводСтроки + ПереводСтроки;
Конец = ПереводСтроки + "--" + Граница + "--" + ПереводСтроки;
Части = Новый Массив;
Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Начало, "UTF-8", Ложь));
Части.Добавить(Данные);
Части.Добавить(ПолучитьДвоичныеДанныеИзСтроки(Конец, "UTF-8", Ложь));
Возврат СоединитьДвоичныеДанные(Части);
КонецФункции
Функция ОтправитьФайл(Соединение, Данные, ИмяФайла) Экспорт
Граница = СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");
Запрос = Новый HTTPЗапрос("/api/upload");
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Граница);
Запрос.УстановитьТелоИзДвоичныхДанных(ТелоMultipart(Данные, ИмяФайла, Граница));
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
Возврат Ответ.КодСостояния;
КонецФункции
Формат(Индекс + 1, "ЧЦ=3; ЧВН=") даёт номера 001, 002, чтобы тома сортировались по имени.ТелоMultipart заголовки части переведены в байты без BOM и склеены с файлом. Строки разделяются парой ВК + ПС, как требует формат multipart.ФайловыеПотоки.ОткрытьДляЧтения и ОткрытьДляЗаписи открывают файл как поток, ЧтениеДанных читает из него блоками, ЗаписьДанных пишет. В памяти одновременно только один блок, поэтому объём файла ограничен диском, а не памятью сервера.
// Общий модуль ДвоичныеДанныеСервер, платформа 8.3.10+
// Склеивает тома в один файл блоками по 4 МБ и на лету считает SHA256.
Функция СклеитьТомаПотоком(ИменаТомов, ИмяРезультата) Экспорт
Хеширование = Новый ХешированиеДанных(ХешФункция.SHA256);
ПотокЗаписи = ФайловыеПотоки.ОткрытьДляЗаписи(ИмяРезультата);
Запись = Новый ЗаписьДанных(ПотокЗаписи);
Попытка
Для Каждого ИмяТома Из ИменаТомов Цикл
ДописатьФайл(ИмяТома, Запись, Хеширование);
КонецЦикла;
Исключение
Запись.Закрыть();
ПотокЗаписи.Закрыть();
ВызватьИсключение;
КонецПопытки;
Запись.Закрыть();
ПотокЗаписи.Закрыть();
Возврат НРег(ПолучитьHexСтрокуИзДвоичныхДанных(Хеширование.ХешСумма));
КонецФункции
Процедура ДописатьФайл(ИмяФайла, Запись, Хеширование)
ПотокЧтения = ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла);
Чтение = Новый ЧтениеДанных(ПотокЧтения);
Попытка
Пока Истина Цикл
Блок = Чтение.Прочитать(4 * 1024 * 1024);
Если Блок.Размер = 0 Тогда
Прервать;
КонецЕсли;
ДанныеБлока = Блок.ПолучитьДвоичныеДанные();
Запись.Записать(ДанныеБлока);
Хеширование.Добавить(ДанныеБлока);
КонецЦикла;
Исключение
Чтение.Закрыть();
ПотокЧтения.Закрыть();
ВызватьИсключение;
КонецПопытки;
Чтение.Закрыть();
ПотокЧтения.Закрыть();
КонецПроцедуры
// Тип файла по первым байтам, а не по расширению
Функция ФорматПоСигнатуре(Данные) Экспорт
Чтение = Новый ЧтениеДанных(Данные);
Первые4Байта = Чтение.Прочитать(4).ПолучитьДвоичныеДанные();
Чтение.Закрыть();
Сигнатура = ВРег(ПолучитьHexСтрокуИзДвоичныхДанных(Первые4Байта));
Если СтрНачинаетсяС(Сигнатура, "25504446") Тогда
Возврат "pdf";
ИначеЕсли СтрНачинаетсяС(Сигнатура, "504B0304") Тогда
Возврат "zip"; // docx и xlsx — тоже ZIP-архивы
ИначеЕсли СтрНачинаетсяС(Сигнатура, "89504E47") Тогда
Возврат "png";
ИначеЕсли СтрНачинаетсяС(Сигнатура, "FFD8FF") Тогда
Возврат "jpg";
КонецЕсли;
Возврат "";
КонецФункции
Чтение.Прочитать(4 * 1024 * 1024) возвращает РезультатЧтенияДанных. Нулевое свойство Размер означает конец файла.ИмяРезультата — новый файл, например из ПолучитьИмяВременногоФайла("zip").Попытка. Незакрытый поток держит файл открытым, и удалить или перезаписать его не получится.ФорматПоСигнатуре читает первые 4 байта и определяет тип по сигнатуре. Переименование меняет расширение, но не первые байты файла.ПолучитьИзВременногоХранилища вернул Неопределено: адрес сохранили в реквизит и прочитали позже, форма уже закрыта или данные поместили без идентификатора формы. Проверяйте ТипЗнч(Данные) и храните в базе сами данные, а не адрес.Объект.ДанныеФайла. Реквизита типа ХранилищеЗначения в данных формы нет — работайте через ТекущийОбъект в ПриЧтенииНаСервере и ПередЗаписьюНаСервере.Файл с диска сервера читают на сервере: Новый ДвоичныеДанные(ПолноеИмяФайла). Файл, который выбирает пользователь, передают через НачатьПомещениеФайла на клиенте и ПолучитьИзВременногоХранилища на сервере — этот вариант работает во всех клиентах, включая веб-клиент.
ДвоичныеДанные — байты, с которыми работает код: запись в файл, Base64, хеш, отправка по HTTP. ХранилищеЗначения — контейнер для записи в реквизит любого сериализуемого значения, в том числе двоичных данных, со сжатием. Значение из него достают методом Получить().
На сервере поместите данные во временное хранилище с идентификатором формы и верните адрес на клиент, как в функции АдресВложения. На клиенте передайте адрес в НачатьПолучениеФайлов, а в конфигурациях на БСП — в ФайловаяСистемаКлиент.СохранитьФайл.
Данные.Размер() возвращает размер в байтах, для мегабайт разделите на 1024 × 1024. Файлы сравнивают по размеру, а при совпадении — по SHA256, как в функции ФайлыОдинаковые. Хеш удобно хранить в реквизите рядом с файлом: новую версию сравнивают с ним, не читая старые байты из базы.
Если загрузку вложений, обмен файлами с API банка или маркетплейса и проверку хешей нужно встроить в рабочую конфигурацию с правами, журналом и обработкой ошибок, посмотрите услуги по доработке 1С. Задачу можно описать в форме «Разместить задание» — исполнители оценят объём и сроки.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.