Программная загрузка файлов в 1С:Предприятие 8.3 — от диалога выбора до разбора Excel

· 10 мин

Содержание

Чтобы загрузить файл в 1С программно, его передают с клиента на сервер через временное хранилище: на клиенте вызывают НачатьПомещениеФайла (или НачатьПомещениеФайлаНаСервер в платформе 8.3.18 и новее), а на сервере забирают содержимое функцией ПолучитьИзВременногоХранилища(Адрес) — она вернёт объект ДвоичныеДанные. Дальше эти данные записывают во временный файл и читают нужным объектом: ТабличныйДокумент для Excel, ЧтениеТекста для CSV, ЧтениеXML или ЧтениеJSON для обменных форматов.

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

Где живёт файл: клиент, сервер и двоичные данные

В клиент-серверной 1С есть два компьютера с разными файловыми системами. Путь C:\Users\buh\Desktop\price.xlsx существует только на машине пользователя. Серверный код выполняется на сервере 1С, и там этого пути нет.

Отсюда три базовых объекта, на которых держится любая загрузка:

  • 📦 ДвоичныеДанные — содержимое файла в памяти. Его можно передать между клиентом и сервером, записать на диск методом Записать(ИмяФайла), получить размер через Размер().
  • 🗄️ Временное хранилище — область на сервере, куда клиент кладёт данные и получает строковый адрес. Сервер по этому адресу забирает данные.
  • 📄 Файл — описание файла на диске: Существует(), Размер(), Расширение, ИмяБезРасширения.

Создать двоичные данные из файла на сервере можно одной строкой:

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

А обратно — записать на диск:

ДанныеФайла.Записать(ПолноеИмяФайла);

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

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

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

Как загрузить файл с диска пользователя на сервер 1С

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

Шаг 1. Команда на клиенте

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

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

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

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

Шаг 2. Обработка результата

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

    Если Не Результат Тогда
        Возврат; // пользователь нажал «Отмена»
    КонецЕсли;

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

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

Шаг 3. Серверная часть

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

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

    ФайлКлиента = Новый Файл(ИмяФайлаНаКлиенте);
    ИмяВременногоФайла = ПолучитьИмяВременногоФайла(Сред(ФайлКлиента.Расширение, 2));
    ДанныеФайла.Записать(ИмяВременногоФайла);

    // ... чтение файла, см. раздел про Excel и CSV

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

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

Сред(ФайлКлиента.Расширение, 2) отрезает точку: свойство Расширение возвращает строку вида .xlsx.

Новый метод в 8.3.18+

В платформе с номером не ниже 8.3.18 появился НачатьПомещениеФайлаНаСервер. Его оповещение получает объект с описанием помещённого файла: в нём есть адрес во временном хранилище и признак отмены (ПомещениеФайлаОтменено). Метод умеет показывать прогресс передачи и вызывать обработчик перед началом. Старый НачатьПомещениеФайла при этом работает, поэтому для типовых задач его можно оставить.

Модальный ПоместитьФайл в новых конфигурациях запрещён настройкой «Режим использования модальности». В веб-клиенте и мобильном клиенте пишите только асинхронный вариант.

Без диалога: файл по известному пути

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

Загрузка файла по HTTP из внешних источников

Когда файл лежит не у пользователя, а на сайте или в API, его забирают сервером напрямую. Клиент здесь не участвует. Объекты: HTTPСоединение, HTTPЗапрос, HTTPОтвет.

Скачать файл по ссылке

&НаСервереБезКонтекста
Функция СкачатьФайл(Сервер, АдресРесурса)

    ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(, Новый СертификатыУдостоверяющихЦентровОС);
    Соединение = Новый HTTPСоединение(Сервер, 443, , , , 60, ЗащищенноеСоединение);

    Запрос = Новый HTTPЗапрос(АдресРесурса);
    Ответ = Соединение.Получить(Запрос);

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

    Возврат Ответ.ПолучитьТелоКакДвоичныеДанные();

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

Вызов: СкачатьФайл("example.ru", "/files/price.csv"). Сервер передаётся без https://, путь — с ведущим слешем.

  • 🔒 СертификатыУдостоверяющихЦентровОС берёт корневые сертификаты из хранилища операционной системы сервера. Если сайт с самоподписанным сертификатом, добавьте его корневой сертификат в доверенные на сервере, а не отключайте проверку.
  • ⏱️ Шестой параметр — таймаут в секундах. Без него зависший внешний сервер подвесит сеанс пользователя.
  • 💾 Для больших файлов передайте вторым параметром Получить имя выходного файла. Тело ответа сразу ляжет на диск, минуя память.
ИмяФайла = ПолучитьИмяВременногоФайла("zip");
Ответ = Соединение.Получить(Запрос, ИмяФайла);

Отправить файл во внешний сервис

Обратная задача — загрузить файл из 1С на чужой сервер. Тело запроса заполняют из двоичных данных или прямо из файла:

Запрос = Новый HTTPЗапрос("/api/upload");
Запрос.Заголовки.Вставить("Content-Type", "application/octet-stream");
Запрос.УстановитьТелоИзДвоичныхДанных(ДанныеФайла);
Ответ = Соединение.ОтправитьДляОбработки(Запрос);

Для PUT и других методов используйте Соединение.ВызватьHTTPМетод("PUT", Запрос). Если сервис ждёт multipart/form-data, тело придётся собрать вручную: границу, заголовки части и двоичные данные склеивают через ЗаписьДанных в поток в памяти.

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

Чтение Excel и CSV после загрузки файла в 1С

Двоичные данные получены и записаны во временный файл. Теперь их надо разобрать.

Excel через табличный документ

Платформа 8.3 читает xlsx и xls сама, без установленного Excel:

ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);

Для НомерСтроки = 2 По ТабДок.ВысотаТаблицы Цикл

    Артикул = СокрЛП(ТабДок.Область(НомерСтроки, 1, НомерСтроки, 1).Текст);
    Наименование = СокрЛП(ТабДок.Область(НомерСтроки, 2, НомерСтроки, 2).Текст);
    ЦенаСтрокой = СокрЛП(ТабДок.Область(НомерСтроки, 3, НомерСтроки, 3).Текст);

    Если ПустаяСтрока(Артикул) Тогда
        Продолжить;
    КонецЕсли;

    Цена = Число(СтрЗаменить(ЦенаСтрокой, " ", ""));
    // поиск номенклатуры по артикулу и запись цены

КонецЦикла;

Цикл начинается со второй строки, потому что первая — шапка. Область(Верх, Лево, Низ, Право) возвращает одну ячейку, если границы совпадают.

  • 📊 СпособЧтенияЗначенийТабличногоДокумента.Значение сохраняет типы ячеек. С вариантом Текст всё придёт строками, как видно на экране, включая разделители разрядов.
  • 🧮 Число из ячейки в формате «1 250,50» содержит неразрывный пробел. Уберите оба вида пробелов перед Число(), иначе получите исключение.
  • 📑 Читается первый лист книги. Нужен другой — сохраните его первым или попросите пользователя прислать файл с одним листом.

Старый способ через Новый COMОбъект("Excel.Application") работает только на Windows с установленным Excel. На сервере под Linux и в веб-клиенте он недоступен, а на нагруженном сервере оставляет зависшие процессы EXCEL.EXE.

CSV и текстовые файлы построчно

Чтение = Новый ЧтениеТекста(ИмяВременногоФайла, КодировкаТекста.UTF8);
Строка = Чтение.ПрочитатьСтроку(); // шапка

Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл

    Поля = СтрРазделить(Строка, ";", Истина);
    Если Поля.Количество() >= 3 Тогда
        Артикул = СокрЛП(Поля[0]);
        Наименование = СокрЛП(Поля[1]);
        Количество = Число(Поля[2]);
    КонецЕсли;

    Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;

Чтение.Закрыть();

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

Выгрузки из старых программ и банковских систем часто в кодировке Windows-1251. Тогда вторым параметром укажите "windows-1251" или КодировкаТекста.ANSI — при неверной кодировке вместо кириллицы будут «кракозябры».

Простое деление по разделителю не понимает кавычек. Если в CSV есть поля вида "ООО ""Ромашка""; склад", точка с запятой внутри кавычек разрежет строку неверно. Для таких файлов пишите разбор посимвольно или просите выгрузку в xlsx.

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

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

Таблица форматов

ФорматЧем читатьНужен файл на диске
xlsx, xls, odsТабличныйДокумент.Прочитатьда
csv, txtЧтениеТекста или ПолучитьСтрокуИзДвоичныхДанныхнет для второго способа
xmlЧтениеXML, ПостроительDOMнет, можно ОткрытьПоток
jsonЧтениеJSON + ПрочитатьJSONнет, можно УстановитьСтроку
dbfXBaseда

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

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

Правила простые:

  1. Имя получайте только через ПолучитьИмяВременногоФайла("расширение"). Платформа гарантирует уникальное имя, и два пользователя не перезапишут файлы друг друга.
  2. Если нужен целый каталог (например, распаковать архив), создайте его сами внутри КаталогВременныхФайлов() с уникальным именем:
ВременныйКаталог = КаталогВременныхФайлов() + Строка(Новый УникальныйИдентификатор);
СоздатьКаталог(ВременныйКаталог);

Архив = Новый ЧтениеZipФайла(ИмяАрхива);
Архив.ИзвлечьВсе(ВременныйКаталог);
Архив.Закрыть();

Файлы = НайтиФайлы(ВременныйКаталог, "*.xml", Истина);
  1. Удаляйте в любом исходе — и при успехе, и при ошибке. Для этого удаление ставят после блока Попытка (пример ниже).
  2. Каталог удаляется вместе с содержимым: УдалитьФайлы(ВременныйКаталог).

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

Обработка ошибок при программной загрузке файла

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

&НаСервере
Процедура ЗагрузитьПрайс(Адрес)

    ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");

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

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

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

Функции ПодробноеПредставлениеОшибки и КраткоеПредставлениеОшибки есть в платформе 8.3.17 и новее. На более старой платформе текст ошибки берите из ОписаниеОшибки(). ОбщегоНазначения.СообщитьПользователю — из БСП; в конфигурации без неё создайте Новый СообщениеПользователю и вызовите Сообщить().

Что проверять до разбора, чтобы ошибка была понятной:

  • 📏 Размер. ДанныеФайла.Размер() = 0 — пустой файл, дальше идти незачем. Ограничьте и верх: прайс на 300 МБ скорее всего не прайс.
  • 🏷️ Расширение. Сверяйте с ожидаемым списком до записи на диск.
  • 🧾 Шапку. Прочитайте первую строку и сравните названия колонок с ожидаемыми. Пользователь, вставивший столбец «Бренд», иначе загрузит бренды вместо цен.
  • 🔢 Типы. Оборачивайте Число() и Дата() в отдельную попытку на уровне строки, копите ошибки с номером строки и показывайте общим списком в конце.

Не записывайте данные в базу по мере чтения файла без транзакции. Если файл упадёт на 500-й строке из 1000, половина цен обновится, половина нет. Либо сначала читайте всё в таблицу значений и проверяйте, либо оборачивайте запись в НачатьТранзакцию() / ЗафиксироватьТранзакцию() с ОтменитьТранзакцию() в блоке исключения.

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

СпособКогда подходитОграничения
НачатьПомещениеФайла с диалогомпользователь сам выбирает файлодин файл за вызов; для нескольких — НачатьПомещениеФайлов
НачатьПомещениеФайлаНаСерверкрупные файлы, нужен прогрессплатформа 8.3.18 и новее
Помещение без диалогапуть известен, тонкий клиентв веб-клиенте нужно расширение работы с файлами
HTTPСоединение.Получитьфайл на сайте или в APIдоступ сервера 1С в интернет, сертификаты
Чтение из общего сетевого каталога на серверерегламентная загрузка без участия людейу учётной записи службы 1С должны быть права на каталог
COMОбъект("Excel.Application")старые обработкитолько Windows и установленный Excel, медленно

Несколько практических наблюдений про производительность:

  • 🚀 Передача клиент — сервер идёт по сети. Файл 50 МБ через медленный VPN грузится минуты, и это не лечится кодом. Сжимайте на стороне источника или забирайте файл сервером по HTTP.
  • 🧠 Двоичные данные целиком лежат в памяти рабочего процесса. Для файлов в сотни мегабайт используйте потоки: ФайловыеПотоки.ОткрытьДляЧтения(ИмяФайла) и ЧтениеДанных читают кусками.
  • 🐢 Обращение к ТабДок.Область() для каждой ячейки на 100 000 строк заметно медленнее, чем выгрузка в CSV и построчное чтение. Для больших прайсов просите CSV.
  • 🔁 Поиск номенклатуры запросом на каждой строке — главный тормоз загрузки. Соберите все артикулы файла в таблицу и найдите их одним запросом с временной таблицей.
  • ⏳ Долгую загрузку запускайте в фоновом задании (в БСП — ДлительныеОперации), чтобы форма не висела и сеанс не упирался в таймаут.

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

Почему на сервере не находится файл, который точно есть у пользователя?
Серверный код ищет путь на диске сервера 1С. Файл нужно передать через временное хранилище методом НачатьПомещениеФайла, а не открывать по пути пользователя.
Как загрузить сразу несколько файлов?
Используйте НачатьПомещениеФайлов. В оповещение придёт массив описаний переданных файлов, у каждого свой адрес во временном хранилище.
Можно ли загрузить файл в регламентном задании?
Да, но только с диска сервера или по HTTP. Клиента в регламентном задании нет, диалоги и методы помещения файлов там недоступны.
Как сохранить загруженный файл в базе, а не на диске?
Положите двоичные данные в реквизит типа ХранилищеЗначения. В конфигурациях на БСП удобнее подсистема «Присоединённые файлы»: она сама решает, хранить файл в базе или в томах на диске.
Чем отличается загрузка в веб-клиенте?
Диалог выбора работает без дополнительных настроек. Чтение файла по пути без диалога и работа с локальными каталогами требуют расширения работы с файлами в браузере.