Программная загрузка файлов в 1С:Предприятие 8.3 — от диалога выбора до разбора Excel
Содержание
Чтобы загрузить файл в 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 | нет, можно УстановитьСтроку |
| dbf | XBase | да |
Временные файлы и каталоги: создать и убрать за собой
Сервер 1С не чистит временные файлы, созданные вашим кодом. Каждая загрузка без удаления оставляет мусор в каталоге временных файлов пользователя, под которым работает служба. За год на активной базе там набираются гигабайты.
Правила простые:
- Имя получайте только через
ПолучитьИмяВременногоФайла("расширение"). Платформа гарантирует уникальное имя, и два пользователя не перезапишут файлы друг друга. - Если нужен целый каталог (например, распаковать архив), создайте его сами внутри
КаталогВременныхФайлов()с уникальным именем:
ВременныйКаталог = КаталогВременныхФайлов() + Строка(Новый УникальныйИдентификатор);
СоздатьКаталог(ВременныйКаталог);
Архив = Новый ЧтениеZipФайла(ИмяАрхива);
Архив.ИзвлечьВсе(ВременныйКаталог);
Архив.Закрыть();
Файлы = НайтиФайлы(ВременныйКаталог, "*.xml", Истина);
- Удаляйте в любом исходе — и при успехе, и при ошибке. Для этого удаление ставят после блока
Попытка(пример ниже). - Каталог удаляется вместе с содержимым:
УдалитьФайлы(ВременныйКаталог).
КаталогВременныхФайлов() возвращает путь с завершающим разделителем. Не добавляйте свой \: на 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. - 🔁 Поиск номенклатуры запросом на каждой строке — главный тормоз загрузки. Соберите все артикулы файла в таблицу и найдите их одним запросом с временной таблицей.
- ⏳ Долгую загрузку запускайте в фоновом задании (в БСП —
ДлительныеОперации), чтобы форма не висела и сеанс не упирался в таймаут.
Частые вопросы
Почему на сервере не находится файл, который точно есть у пользователя?
НачатьПомещениеФайла, а не открывать по пути пользователя.Как загрузить сразу несколько файлов?
НачатьПомещениеФайлов. В оповещение придёт массив описаний переданных файлов, у каждого свой адрес во временном хранилище.Можно ли загрузить файл в регламентном задании?
Как сохранить загруженный файл в базе, а не на диске?
ХранилищеЗначения. В конфигурациях на БСП удобнее подсистема «Присоединённые файлы»: она сама решает, хранить файл в базе или в томах на диске.