ДвоичныеДанные в 1С 8.3: загрузка, сохранение, потоки и передача файлов
Содержание
Чтобы поместить файл в двоичные данные 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.