Поток своей сохраненной карточки: Ждать и callback с UUID-запросом

Учебные задачи

Полный учебный маршрут создает реальное собственное обсуждение и сообщение с двумя UTF-8 вложениями. Работает с коллекцией как редактором нового сообщения, затем отдельно читает серверный результат и сравнивает байты через sync, Ждать и ОписаниеОповещения.

Где и как запускать

Полный модуль собственной управляемой формы, толстый клиент/управляемое приложение. Серверный поддерживающий модуль УчебныеВложения85 обязателен и приведен полностью. Каждый модуль вставляется отдельно в свое место, не склеивается.

Подготовка

  1. Используйте отдельную VM, платформу8.5.1.1343 и новую пустую конфигурацию УчебноеВзаимодействие85: русский язык, управляемый интерфейс, совместимость Не использовать. Предметных объектов/регламентных заданий/подписок/расширений нет. Не запускайте в УНФ/КА/УТ/БП или на рабочем сервере.

  2. Вручную разверните отдельный собственный сервер взаимодействия по документации своей серверной поставки. Единственный разрешенный адрес регистрации http://localhost:9094; никаких внешних мессенджеров, SMTP, общего абонента или публичной публикации. Адрес с другим хостом намеренно не принимается.

  3. На этом же стенде вручную подключите внешнее файловое хранилище только с собственными учебными учетными данными и локальной конечной точкой localhost в этой VM. Создайте собственный пустой учебный бакет; не используйте рабочие S3-ключи/бакеты. Формат серверного конфигурационного файла зависит от отдельной поставки сервера и здесь не придумывается.

  4. В справке ПоддерживаетсяРаботаСВложениями означает подключение файлового хранилища, а не его доступность по сети или достаточную квоту. Локальные разрешения/вместимость/срок хранения проверьте вручную; отключите короткое удаление только на своем пустом стенде. Без хранилища код отказывает до создания обсуждения.

  5. В конфигураторе создайте ровно одного своего пользователя Уч85Связь_<ваш новый канонический ненулевой UUID>, полное имя Учебный участник. Права назначаются вручную только в собственном стенде. Войдите под ним; его реальный IB UUID будет сверяться в обе стороны с реальным ID системы взаимодействия.

  6. Вручную зарегистрируйте свое приложение под именем УчебноеВзаимодействие85. Свой пользователь внутренний, не заблокирован, без внешнего ID/почты/телефона/ключа сопоставления. Он должен совпадать с реальным пользователем базы. Ни аккаунты, ни права этот образец не создает.

  7. Создайте серверный общий модуль УчебныеВложения85, только флаг Сервер; Привилегированный выключен. Вставьте главный модуль полностью. Здесь включены проверка пустой среды, паспорт своего пользователя/приложения, создание данных и проверка потока.

  8. Создайте свою внешнюю обработку с управляемой формой и реквизитом Подтверждение типа Строка длиной100. Добавьте команды Подготовить, ПрочитатьЖдать, ПрочитатьЧерезОповещение и назначьте одноименные обработчики. В модуль формы вставьте полный поддерживающий код. Установите реквизит формы Подтверждение в доступное поле.

  9. Откройте форму в толстом клиенте, управляемое приложение. Сам AsyncOpenStream доступен также thin/web, но полный маршрут сознательно использует толстый клиент для синхронного GetMessage/получения обсуждения; серверный модуль не переносится на клиент.

  10. Сначала оставьте Подтверждение пустым: подготовка должна завершиться исключением без создания обсуждения. Закройте и заново откройте форму; после прочтения предупреждений введите СВОИ ДВА ВЛОЖЕНИЯ и нажмите Подготовить один раз. Флаг повторного запуска выставляется до серверного вызова.

  11. Оставьте только этот собственный сеанс и не меняйте созданную карточку параллельно. Вложения действительно передаются в свое файловое хранилище при ручном запуске. Метод Очистить показан только на отдельном новом неотправленном черновике, а не на сохраненном сообщении.

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

Перем СвойПакет85;
Перем НачатОпыт85;
Перем ЗапросЧтения85;
Перем Занято85;

&НаКлиенте
Процедура Подготовить(Команда)
    #Если ТолстыйКлиентУправляемоеПриложение Тогда
        Если НачатОпыт85 = Истина Тогда
            ВызватьИсключение "Повтор подготовки запрещен; проверьте возможный частичный результат вручную";
        КонецЕсли;
        НачатОпыт85 = Истина;
        СвойПакет85 = ПодготовитьНаСервере85();
        Сообщить("Созданы и проверены свои два вложения. Доступны кнопки Ждать и ЧерезОповещение");
    #Иначе
        ВызватьИсключение "Полный образец использует синхронный GetMessage: нужен толстый управляемый клиент";
    #КонецЕсли
КонецПроцедуры

&НаСервере
Функция ПодготовитьНаСервере85()
    Возврат УчебныеВложения85.ПодготовитьСвоиВложения85(Подтверждение);
КонецФункции

&НаКлиенте
Асинх Процедура ПрочитатьЖдать(Команда)
    #Если ТолстыйКлиентУправляемоеПриложение Тогда
        Если Занято85 = Истина Тогда
            ВызватьИсключение "Предыдущее чтение еще не закончено";
        КонецЕсли;
        Вложение = ПолучитьСвоюКарточку85();
        ОжидаемыеБайты = ПолучитьДвоичныеДанныеИзСтроки(СвойПакет85.Ключ + "|Книга|3|250|750", "UTF-8", Ложь);
        Занято85 = Истина;
        Попытка
            ПотокТела = Ждать Вложение.ОткрытьПотокДляЧтенияАсинх();
            ПроверитьИЗакрытьСвойПоток85(ПотокТела, ОжидаемыеБайты);
        Исключение
            Занято85 = Ложь;
            ВызватьИсключение;
        КонецПопытки;
        Занято85 = Ложь;
        Сообщить("Ждать вернул настоящий поток: все байты своей карточки совпали");
    #Иначе
        ВызватьИсключение "Нужен толстый управляемый клиент";
    #КонецЕсли
КонецПроцедуры

&НаКлиенте
Процедура ПрочитатьЧерезОповещение(Команда)
    #Если ТолстыйКлиентУправляемоеПриложение Тогда
        Если Занято85 = Истина Тогда
            ВызватьИсключение "Предыдущее чтение еще не закончено";
        КонецЕсли;
        Вложение = ПолучитьСвоюКарточку85();
        ЗапросЧтения85 = Строка(Новый УникальныйИдентификатор);
        Параметры = Новый Структура("Запрос,Форма,Ключ", ЗапросЧтения85, Строка(УникальныйИдентификатор), СвойПакет85.Ключ);
        Оповещение = Новый ОписаниеОповещения("СвойПотокОткрыт85", ЭтотОбъект, Параметры, "ОшибкаСвоегоПотока85", ЭтотОбъект);
        Занято85 = Истина;
        Попытка
            Вложение.НачатьОткрытиеПотокаДляЧтения(Оповещение);
        Исключение
            Занято85 = Ложь;
            ВызватьИсключение;
        КонецПопытки;
    #Иначе
        ВызватьИсключение "Нужен толстый управляемый клиент";
    #КонецЕсли
КонецПроцедуры

&НаКлиенте
Процедура СвойПотокОткрыт85(ПотокТела, ДополнительныеПараметры) Экспорт
    Если ДополнительныеПараметры.Форма <> Строка(УникальныйИдентификатор)
        Или ДополнительныеПараметры.Запрос <> ЗапросЧтения85
        Или СвойПакет85 = Неопределено Или ДополнительныеПараметры.Ключ <> СвойПакет85.Ключ Тогда
        Если ТипЗнч(ПотокТела) = Тип("Поток") Тогда
            ПотокТела.Закрыть();
        КонецЕсли;
        Возврат;
    КонецЕсли;
    Занято85 = Ложь;
    ОжидаемыеБайты = ПолучитьДвоичныеДанныеИзСтроки(ДополнительныеПараметры.Ключ + "|Книга|3|250|750", "UTF-8", Ложь);
    ПроверитьИЗакрытьСвойПоток85(ПотокТела, ОжидаемыеБайты);
    Сообщить("Оповещение передало настоящий поток: все байты своей карточки совпали");
КонецПроцедуры

&НаКлиенте
Процедура ОшибкаСвоегоПотока85(ИнформацияОбОшибке, ДополнительныеПараметры) Экспорт
    Если ДополнительныеПараметры.Форма = Строка(УникальныйИдентификатор)
        И ДополнительныеПараметры.Запрос = ЗапросЧтения85 Тогда
        Занято85 = Ложь;
        Сообщить("Открытие своего потока не завершилось; сырые сведения об ошибке не выводятся");
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Функция ПолучитьСвоюКарточку85()
    #Если ТолстыйКлиентУправляемоеПриложение Тогда
        Если СвойПакет85 = Неопределено Тогда
            ВызватьИсключение "Сначала создайте собственную карточку через Подготовить";
        КонецЕсли;
        Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
            Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094"
            Или Строка(СистемаВзаимодействия.ИдентификаторТекущегоПользователя()) <> СвойПакет85.Автор Тогда
            ВызватьИсключение "Собственный сервер или текущий пользователь изменен";
        КонецЕсли;
        ИДОбсуждения = Новый ИдентификаторОбсужденияСистемыВзаимодействия(СвойПакет85.Обсуждение);
        Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение(СвойПакет85.Ключ);
        Если Обсуждение = Неопределено Тогда
            ВызватьИсключение "Собственное обсуждение не найдено";
        КонецЕсли;
        Если Обсуждение.Идентификатор <> ИДОбсуждения Или Обсуждение.Ключ <> СвойПакет85.Ключ
            Или Обсуждение.Интеграция <> Неопределено Или Обсуждение.КонтекстОбсуждения <> Неопределено
            Или Обсуждение.Участники.Количество() <> 1
            Или Не Обсуждение.Участники.Содержит(СистемаВзаимодействия.ИдентификаторТекущегоПользователя()) Тогда
            ВызватьИсключение "Состав собственного обсуждения изменен";
        КонецЕсли;
        ИДСообщения = Новый ИдентификаторСообщенияСистемыВзаимодействия(СвойПакет85.Сообщение);
        Сообщение = СистемаВзаимодействия.ПолучитьСообщение(ИДСообщения);
        Если Сообщение.Идентификатор <> ИДСообщения Или Сообщение.Обсуждение <> ИДОбсуждения
            Или Строка(Сообщение.Автор) <> СвойПакет85.Автор Или Сообщение.Данные <> СвойПакет85.Ключ + "|Книга|3|250|750"
            Или Сообщение.Вложения.Количество() <> 2 Тогда
            ВызватьИсключение "Паспорт сохраненной собственной карточки изменен";
        КонецЕсли;
        Вложение = Сообщение.Вложения.Получить(0);
        ОжидаемыеБайты = ПолучитьДвоичныеДанныеИзСтроки(СвойПакет85.Ключ + "|Книга|3|250|750", "UTF-8", Ложь);
        Если Вложение.Наименование <> СвойПакет85.Ключ + "-card.txt" Или Вложение.ТипСодержимого <> "text/plain"
            Или Не Вложение.Отображаемое Или Вложение.СодержимоеУдалено Или Вложение.Размер <> ОжидаемыеБайты.Размер() Тогда
            ВызватьИсключение "Собственное вложение изменено или уже удалено хранилищем";
        КонецЕсли;
        Возврат Вложение;
    #Иначе
        ВызватьИсключение "Нужен толстый управляемый клиент";
    #КонецЕсли
КонецФункции

&НаКлиенте
Процедура ПроверитьИЗакрытьСвойПоток85(ПотокТела, ОжидаемыеБайты)
    Если ТипЗнч(ПотокТела) <> Тип("Поток") Тогда
        ВызватьИсключение "Ожидался реальный поток вложения";
    КонецЕсли;
    Попытка
        Если Не ПотокТела.ДоступноЧтение Тогда
            ВызватьИсключение "Поток вложения недоступен для чтения";
        КонецЕсли;
        ОжидаемыйБуфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ОжидаемыеБайты);
        Буфер = Новый БуферДвоичныхДанных(128);
        Смещение = 0;
        Пока Истина Цикл
            Прочитано = ПотокТела.Прочитать(Буфер, 0, 128);
            Если Прочитано = 0 Тогда
                Прервать;
            КонецЕсли;
            Если ТипЗнч(Прочитано) <> Тип("Число") Или Прочитано < 0 Или Прочитано > 128
                Или Смещение + Прочитано > ОжидаемыеБайты.Размер() Тогда
                ВызватьИсключение "Длина потока собственного вложения неверна";
            КонецЕсли;
            Для Индекс = 0 По Прочитано - 1 Цикл
                Если Буфер[Индекс] <> ОжидаемыйБуфер[Смещение + Индекс] Тогда
                    ВызватьИсключение "Байты собственного вложения отличаются";
                КонецЕсли;
            КонецЦикла;
            Смещение = Смещение + Прочитано;
        КонецЦикла;
        Если Смещение <> ОжидаемыеБайты.Размер() Тогда
            ВызватьИсключение "Поток собственного вложения завершился преждевременно";
        КонецЕсли;
    Исключение
        ПотокТела.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    ПотокТела.Закрыть();
КонецПроцедуры
Полный серверный общий модуль УчебныеВложения85: собственные реальные данные и все проверки

Собственная конфигурация → Общие → Общие модули → УчебныеВложения85.

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

Функция ПодготовитьСвоиВложения85(Подтверждение) Экспорт
    Пользователь = ПроверитьСвоегоПользователя85();
    Если Подтверждение <> "СВОИ ДВА ВЛОЖЕНИЯ" Тогда
        ВызватьИсключение "Сначала подготовьте свой стенд. Ничего не создано";
    КонецЕсли;
    Если Не СистемаВзаимодействия.ПоддерживаетсяРаботаСВложениями() Тогда
        ВызватьИсключение "На своем сервере нет внешнего файлового хранилища";
    КонецЕсли;
    Ключ = "Уч85Вложения_" + Строка(Новый УникальныйИдентификатор);
    Сообщить("Свой кандидат обсуждения: " + Ключ);
    Если СистемаВзаимодействия.ПолучитьОбсуждение(Ключ) <> Неопределено Тогда
        ВызватьИсключение "Ключ занят. Существующие данные не изменяются";
    КонецЕсли;
    Обсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
    Обсуждение.Ключ = Ключ;
    Обсуждение.Заголовок = Ключ + " | Книга: 3 по 250";
    Обсуждение.Групповое = Истина;
    Обсуждение.Отображаемое = Истина;
    Обсуждение.ТолькоПросмотр = Ложь;
    Обсуждение.КонтекстОбсуждения = Неопределено;
    Обсуждение.Интеграция = Неопределено;
    Обсуждение.ВнешнийИдентификатор = "";
    Обсуждение.Участники.Добавить(Пользователь.Идентификатор);
    Обсуждение.Записать();
    ИДОбсуждения = Обсуждение.Идентификатор;
    ПроверитьСвоеОбсуждениеВложений85(ИДОбсуждения, Ключ, Пользователь.Идентификатор);
    Сообщение = СистемаВзаимодействия.СоздатьСообщение(ИДОбсуждения);
    Сообщение.Текст = Ключ + " | Книга: 3 по 250 = 750";
    Сообщение.Данные = Ключ + "|Книга|3|250|750";
    Сообщение.ФорматДанныхСтроки = "text/plain";
    Сообщение.Получатели.Добавить(Новый ИдентификаторПользователяСистемыВзаимодействия);
    Если Сообщение.Вложения.Количество() <> 0 Тогда
        ВызватьИсключение "Новый объект сообщения уже содержит вложения";
    КонецЕсли;
    БайтыКарточки = ПолучитьДвоичныеДанныеИзСтроки(Ключ + "|Книга|3|250|750", "UTF-8", Ложь);
    БайтыПроверки = ПолучитьДвоичныеДанныеИзСтроки(Ключ + "|Проверка|750", "UTF-8", Ложь);
    БайтыЧерновика = ПолучитьДвоичныеДанныеИзСтроки(Ключ + "|Черновик", "UTF-8", Ложь);
    ПотокКарточки = БайтыКарточки.ОткрытьПотокДляЧтения();
    ПотокПроверки = БайтыПроверки.ОткрытьПотокДляЧтения();
    ПотокЧерновика = БайтыЧерновика.ОткрытьПотокДляЧтения();
    Попытка
        Вложения = Сообщение.Вложения;
        Вложения.Добавить(ПотокКарточки, Ключ + "-card.txt", "text/plain", Истина);
        Карточка = Вложения.Получить(0);
        Проверка = Вложения.Вставить(0, ПотокПроверки, Ключ + "-check.bin", "application/octet-stream", Ложь);
        Вложения.Добавить(ПотокЧерновика, Ключ + "-draft.txt", "text/plain", Истина);
        Если Вложения.Количество() <> 3 Или Вложения.Индекс(Карточка) <> 1
            Или Вложения.Индекс(Проверка) <> 0 Тогда
            ВызватьИсключение "Вставка своих вложений не подтверждена";
        КонецЕсли;
        Вложения.Переместить(Карточка, 0);
        Вложения.Сдвинуть(2, -1);
        Если Вложения.Индекс(Карточка) <> 0 Или Вложения.Индекс(Проверка) <> 2
            Или Вложения.Получить(1).Наименование <> Ключ + "-draft.txt" Тогда
            ВызватьИсключение "Порядок своих вложений неверен";
        КонецЕсли;
        Вложения.Удалить(1);
        Если Вложения.Количество() <> 2 Или Вложения.Получить(0).Наименование <> Ключ + "-card.txt"
            Или Вложения.Получить(1).Наименование <> Ключ + "-check.bin" Тогда
            ВызватьИсключение "Из своего нового сообщения удалено неверное вложение";
        КонецЕсли;
        ПроверитьСвоеОбсуждениеВложений85(ИДОбсуждения, Ключ, Пользователь.Идентификатор);
        Сообщение.Записать();
    Исключение
        ПотокКарточки.Закрыть();
        ПотокПроверки.Закрыть();
        ПотокЧерновика.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    ПотокКарточки.Закрыть();
    ПотокПроверки.Закрыть();
    ПотокЧерновика.Закрыть();
    ИДСообщения = Сообщение.Идентификатор;
    Если ТипЗнч(ИДСообщения) <> Тип("ИдентификаторСообщенияСистемыВзаимодействия") Тогда
        ВызватьИсключение "Запись могла произойти, но реальный ID сообщения не получен";
    КонецЕсли;
    Факт = СистемаВзаимодействия.ПолучитьСообщение(ИДСообщения);
    Если Факт.Идентификатор <> ИДСообщения Или Факт.Обсуждение <> ИДОбсуждения
        Или Факт.Автор <> Пользователь.Идентификатор Или Факт.Данные <> Ключ + "|Книга|3|250|750"
        Или Факт.Текст <> Ключ + " | Книга: 3 по 250 = 750" Или Факт.Вложения.Количество() <> 2 Тогда
        ВызватьИсключение "Собственное сохраненное сообщение не подтверждено";
    КонецЕсли;
    ПроверитьСвойПаспортВложения85(Факт.Вложения.Получить(0), Ключ + "-card.txt", "text/plain", Истина, БайтыКарточки);
    ПроверитьСвойПаспортВложения85(Факт.Вложения.Получить(1), Ключ + "-check.bin", "application/octet-stream", Ложь, БайтыПроверки);
    Для Каждого Вложение Из Факт.Вложения Цикл
        Если Вложение.СодержимоеУдалено Тогда
            ВызватьИсключение "Содержимое уже удалено файловым хранилищем";
        КонецЕсли;
    КонецЦикла;
    ПроверитьИЗакрытьСвойПоток85(Факт.Вложения.Получить(0).ОткрытьПотокДляЧтения(), БайтыКарточки);
    ПроверитьИЗакрытьСвойПоток85(Факт.Вложения.Получить(1).ОткрытьПотокДляЧтения(), БайтыПроверки);
    Черновик = СистемаВзаимодействия.СоздатьСообщение(ИДОбсуждения);
    ПотокОтдельногоЧерновика = БайтыЧерновика.ОткрытьПотокДляЧтения();
    Попытка
        Черновик.Вложения.Добавить(ПотокОтдельногоЧерновика, Ключ + "-unsent.txt", "text/plain", Истина);
        Если Черновик.Вложения.Количество() <> 1 Тогда
            ВызватьИсключение "Свой отдельный черновик не заполнен";
        КонецЕсли;
        Черновик.Вложения.Очистить();
        Если Черновик.Вложения.Количество() <> 0 Тогда
            ВызватьИсключение "Свой отдельный черновик не очищен";
        КонецЕсли;
    Исключение
        ПотокОтдельногоЧерновика.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    ПотокОтдельногоЧерновика.Закрыть();
    Возврат Новый Структура("Ключ,Обсуждение,Сообщение,Автор", Ключ, Строка(ИДОбсуждения), Строка(ИДСообщения), Строка(Пользователь.Идентификатор));
КонецФункции

Процедура ПроверитьСвоеОбсуждениеВложений85(ИДОбсуждения, Ключ, ИДПользователя)
    Если ТипЗнч(ИДОбсуждения) <> Тип("ИдентификаторОбсужденияСистемыВзаимодействия") Тогда
        ВызватьИсключение "Нужен полученный от сервера ID собственного обсуждения";
    КонецЕсли;
    Факт = СистемаВзаимодействия.ПолучитьОбсуждение(Ключ);
    Если Факт = Неопределено Тогда
        ВызватьИсключение "Собственное обсуждение не найдено по ключу";
    КонецЕсли;
    Если Факт.Идентификатор <> ИДОбсуждения Или Факт.Ключ <> Ключ
        Или Факт.Заголовок <> Ключ + " | Книга: 3 по 250" Или Не Факт.Групповое
        Или Не Факт.Отображаемое Или Факт.ТолькоПросмотр Или Факт.КонтекстОбсуждения <> Неопределено
        Или Факт.Интеграция <> Неопределено Или Факт.ВнешнийИдентификатор <> ""
        Или Факт.Участники.Количество() <> 1 Или Не Факт.Участники.Содержит(ИДПользователя) Тогда
        ВызватьИсключение "Паспорт собственного обсуждения изменен";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьСвойПаспортВложения85(Вложение, Имя, MIME, Отображать, Байты)
    Если ТипЗнч(Вложение) <> Тип("ВложениеСистемыВзаимодействия") Тогда
        ВызватьИсключение "Ожидалось настоящее вложение сохраненного сообщения";
    КонецЕсли;
    Если Вложение.Наименование <> Имя Или Вложение.ТипСодержимого <> MIME
        Или Вложение.Отображаемое <> Отображать Или Вложение.Размер <> Байты.Размер()
        Или Вложение.СодержимоеУдалено Тогда
        ВызватьИсключение "Паспорт собственного вложения не совпал";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьИЗакрытьСвойПоток85(ПотокТела, ОжидаемыеБайты)
    Если ТипЗнч(ПотокТела) <> Тип("Поток") Тогда
        ВызватьИсключение "Ожидался реальный поток вложения";
    КонецЕсли;
    Попытка
        Если Не ПотокТела.ДоступноЧтение Тогда
            ВызватьИсключение "Поток вложения недоступен для чтения";
        КонецЕсли;
        ОжидаемыйБуфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ОжидаемыеБайты);
        Буфер = Новый БуферДвоичныхДанных(128);
        Смещение = 0;
        Пока Истина Цикл
            Прочитано = ПотокТела.Прочитать(Буфер, 0, 128);
            Если Прочитано = 0 Тогда
                Прервать;
            КонецЕсли;
            Если ТипЗнч(Прочитано) <> Тип("Число") Или Прочитано < 0 Или Прочитано > 128
                Или Смещение + Прочитано > ОжидаемыеБайты.Размер() Тогда
                ВызватьИсключение "Длина потока собственного вложения неверна";
            КонецЕсли;
            Для Индекс = 0 По Прочитано - 1 Цикл
                Если Буфер[Индекс] <> ОжидаемыйБуфер[Смещение + Индекс] Тогда
                    ВызватьИсключение "Байты собственного вложения отличаются";
                КонецЕсли;
            КонецЦикла;
            Смещение = Смещение + Прочитано;
        КонецЦикла;
        Если Смещение <> ОжидаемыеБайты.Размер() Тогда
            ВызватьИсключение "Поток собственного вложения завершился преждевременно";
        КонецЕсли;
    Исключение
        ПотокТела.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    ПотокТела.Закрыть();
КонецПроцедуры

Функция ПроверитьСвоегоПользователя85()
    ПроверитьСвойПустойСтенд();
    Если ПривилегированныйРежим() Тогда
        ВызватьИсключение "Входной привилегированный режим не допускается";
    КонецЕсли;
    Пользователи = ПользователиИнформационнойБазы.ПолучитьПользователей();
    Текущий = ПользователиИнформационнойБазы.ТекущийПользователь();
    Если Пользователи.Количество() <> 1 Или Текущий = Неопределено Тогда
        ВызватьИсключение "Нужен единственный собственный учебный пользователь";
    КонецЕсли;
    Если Пользователи[0].УникальныйИдентификатор <> Текущий.УникальныйИдентификатор
        Или Лев(Текущий.Имя, СтрДлина("Уч85Связь_")) <> "Уч85Связь_" Тогда
        ВызватьИсключение "Учебный пользователь не подтвержден";
    КонецЕсли;
    UUIDМетки = Новый УникальныйИдентификатор(Сред(Текущий.Имя, СтрДлина("Уч85Связь_") + 1));
    Если Текущий.Имя <> "Уч85Связь_" + Строка(UUIDМетки)
        Или UUIDМетки = Новый УникальныйИдентификатор("00000000-0000-0000-0000-000000000000") Тогда
        ВызватьИсключение "Имя должно содержать собственный канонический ненулевой UUID";
    КонецЕсли;
    Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
        Или Не СистемаВзаимодействия.ИспользованиеДоступно() Тогда
        ВызватьИсключение "Вручную подготовьте регистрацию собственной учебной базы";
    КонецЕсли;
    Если СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094" Тогда
        ВызватьИсключение "Разрешен только отдельно подготовленный сервер localhost:9094 в своей VM";
    КонецЕсли;
    Версия = СистемаВзаимодействия.ВерсияСервера();
    Если ТипЗнч(Версия) <> Тип("Строка") Или Не ЗначениеЗаполнено(Версия) Тогда
        ВызватьИсключение "Версия своего сервера не получена";
    КонецЕсли;
    ИД = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
    Если ТипЗнч(ИД) <> Тип("ИдентификаторПользователяСистемыВзаимодействия") Тогда
        ВызватьИсключение "Не получен реальный идентификатор собственного участника";
    КонецЕсли;
    Если СистемаВзаимодействия.ПолучитьИдентификаторПользователя(Текущий.УникальныйИдентификатор) <> ИД
        Или СистемаВзаимодействия.ПолучитьИдентификаторПользователяИнформационнойБазы(ИД) <> Текущий.УникальныйИдентификатор Тогда
        ВызватьИсключение "Двустороннее соответствие своей учетной записи неверно";
    КонецЕсли;
    Пользователь = СистемаВзаимодействия.ПолучитьПользователя(ИД);
    Если Пользователь.Идентификатор <> ИД Или Пользователь.Имя <> Текущий.Имя
        Или Пользователь.ПолноеИмя <> Текущий.ПолноеИмя
        Или Пользователь.ИдентификаторПользователяИнформационнойБазы <> Текущий.УникальныйИдентификатор Тогда
        ВызватьИсключение "Паспорт своего пользователя не совпал с информационной базой";
    КонецЕсли;
    Если Пользователь.Внешний Или Пользователь.Заблокирован
        Или Пользователь.ТипВнешнейСистемы <> ""
        Или Пользователь.ИдентификаторПользователяВнешнейСистемы <> Неопределено
        Или Пользователь.АдресЭлектроннойПочты <> ""
        Или Пользователь.АдресЭлектроннойПочтыДляАутентификации <> ""
        Или Пользователь.НомерТелефона <> ""
        Или Пользователь.КлючСопоставления <> "" Тогда
        ВызватьИсключение "Нужен чистый внутренний пользователь без персональных контактов и объединений";
    КонецЕсли;
    Если Пользователь.АдминистраторАбонента <> СистемаВзаимодействия.ТекущийПользовательЯвляетсяАдминистраторомАбонента() Тогда
        ВызватьИсключение "Признак администратора абонента не согласован";
    КонецЕсли;
    ВсеПользователи = СистемаВзаимодействия.СтандартныеПользователи.ВсеПользователиПриложения;
    Если ИД = ВсеПользователи Или ИД = Новый ИдентификаторПользователяСистемыВзаимодействия Тогда
        ВызватьИсключение "Стандартный адресат и Никого не оповещать не являются своей учетной записью";
    КонецЕсли;
    ВосстановленныйИД = Новый ИдентификаторПользователяСистемыВзаимодействия(Строка(ИД));
    Если СистемаВзаимодействия.ПолучитьПользователя(ВосстановленныйИД).Идентификатор <> ИД Тогда
        ВызватьИсключение "Восстановленный из строки реальный ID не подтвержден сервером";
    КонецЕсли;
    Приложение = СистемаВзаимодействия.ПолучитьПриложение(СистемаВзаимодействия.ИдентификаторТекущегоПриложения());
    Если Приложение.Идентификатор <> СистемаВзаимодействия.ИдентификаторТекущегоПриложения()
        Или Приложение.Наименование <> "УчебноеВзаимодействие85" Тогда
        ВызватьИсключение "Нужно свое приложение с точным учебным наименованием";
    КонецЕсли;
    Возврат Пользователь;
КонецФункции

Процедура ПроверитьСвойПустойСтенд()
    Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
        ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
    КонецЕсли;
    Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
        ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
    КонецЕсли;
    Источники = Новый Массив;
    Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
    Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
    Для Каждого Источник Из Источники Цикл
        Расширения = РасширенияКонфигурации.Получить(, Источник);
        Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
            ВызватьИсключение "Native список расширений должен иметь тип Массив";
        КонецЕсли;
        Если Расширения.Количество() <> 0 Тогда
            ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
        КонецЕсли;
    КонецЦикла;
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
    КонецЕсли;
КонецПроцедуры

Разбор по шагам

  1. Сначала проверяется пустая конфигурация, отсутствие привилегированного режима и собственный real-user/real-application паспорт. Адрес localhost сам по себе не доказывает владельца сервера: VM и регистрацию проверяете вы.

  2. После точной фразы и проверки поддержки файлов новый UUID-ключ используется только для собственного нового обсуждения. Перед записью сообщения свежая копия обсуждения проверяет единственного своего участника, отсутствие внешней интеграции и контекста.

  3. Книга: 3 по 250 дает750. Карточка содержит свой ключ и этот расчет; второе двоичное вложение содержит собственную проверку750. UTF-8 без BOM и MIME передаются явно; расширение .bin не является доказательством содержимого.

  4. Добавить не объявлен в native-справке как возвращающий вложение: результат не присваивается. Объект карточки получаем через Получить(0). Вставить возвращает новый настоящий объект проверки и ставит его в начало.

  5. Третий собственный черновик нужен для реальной задачи редактирования списка. Последовательность: [проверка,карточка,черновик] → Переместить → [карточка,проверка,черновик] → Сдвинуть → [карточка,черновик,проверка] → Удалить(1) → [карточка,проверка].

  6. После каждого этапа проверяются Количество/Индекс/Получить и точные имена своих элементов. Индексы начинаются с0. Удаляется только собственный лишний черновик нового сообщения, а не файл по произвольному пути.

  7. Все исходные потоки остаются открыты до завершения Записать и закрываются как при успехе, так и при исключении. При ошибке записи неизвестен частичный результат: код не повторяет отправку и не делает автоматический откат.

  8. ПолучитьСообщение читает реальный ID, полученный после записи. Сверяем автора, обсуждение, UUID-маркер, текст и количество вложений, затем каждый readonly паспорт: имя, MIME, размер в байтах, отображение, содержимое удалено.

  9. Серверный OpenStreamForRead открывает сохраненное содержимое. Чтение порциями128 байт сравнивает только фактически прочитанный участок. Короткий ненулевой read не равен концу; EOF только0. Нельзя полагаться на Size/Seek у произвольного потока.

  10. Для Очистить создается отдельный новый объект сообщения и новый поток черновика. После удаления всех его вложений проверяется0; Записать для этого черновика отсутствует. Возможные временные объекты хранилища зависят от серверной поставки.

  11. Команда ПрочитатьЖдать повторно проверяет текущего пользователя/локальный адрес/паспорт сохраненной карточки. Ждать превращает обещание в Поток, затем тот же полный byte-check закрывает поток.

  12. Команда ПрочитатьЧерезОповещение передает request UUID и form UUID. Подпись успеха ровно (ПотокТела,ДополнительныеПараметры); ошибка имеет (ИнформацияОбОшибке,ДополнительныеПараметры). Запоздалый чужой callback не обрабатывает карточку и закрывает полученный поток.

Как проверить результат

  1. Без точного подтверждения/с правильным пользователем, но без файлового хранилища, обсуждение и сообщение не создаются.

  2. У сохраненного собственного сообщения ровно два вложения: <ключ>-card.txt показано, <ключ>-check.bin скрыто. Скрытое тоже реально сохраняется; это не секретность/ограничение доступа.

  3. Ни один readonly паспорт и readonly свойство Сообщение.Вложения не присваивается. Изменяется только содержимое коллекции своего нового объекта.

  4. После успешного серверного чтения и каждой клиентской команды все байты собственного UUID-маркера/расчета750 совпадают, а потоки закрыты.

  5. Измененное имя/MIME/размер/тело, добавленный участник, чужой автор или СодержимоеУдалено вызывают отказ. СодержимоеУдалено нельзя установить для имитации; это readonly состояние настоящего хранилища.

  6. Возврат обещания или вызов callback сам по себе не доказывает корректность содержимого: положительный результат выдается только после точного чтения всех байтов до EOF.

Ожидаемый результат

При ручном успешном запуске на своем подготовленном стенде остается одно новое обсуждение с собственной карточкой книги и двумя проверенными вложениями; отдельный очищенный черновик не отправляется. Три API-стиля читают одинаковые реальные байты750. Здесь подтверждены исходный код/нативные сигнатуры и статические проверки, но runtime1С отсутствует.

На что обратить внимание

  1. localhost не заменяет проверку выделенного сервера, абонента и локального бакета. Подключенное хранилище может быть недоступно; исключение не означает, что запись не произошла частично.

  2. Вложения — readonly коллекция свойства сообщения, но ее методы изменяют состав нового сообщения. Нельзя присвоить новую коллекцию или создавать Вложение/Коллекция через неподтвержденный конструктор.

  3. У Добавить нет документированного return, у Вставить есть ВложениеСистемыВзаимодействия. Не копируйте произвольные параметры из похожих коллекций.

  4. Размер означает байты, не СтрДлина; UTF-8 кириллицы занимает несколько байтов. MIME/Отображаемое не проверяют и не защищают содержимое.

  5. Удалить(индекс)/Очистить здесь только собственные новые объекты. Применение к чужому/сохраненному сообщению требует отдельного согласованного сценария и проверки прав; этот пример такого разрешения не дает.

  6. Синхронный OpenStreamForRead отсутствует в thin/web. Async/callback API доступны клиенту, но весь пример с GetMessage ограничен толстым управляемым клиентом честно.

  7. Поток не читается огромным неограниченным массивом и не сохраняется в файл. Сравнение с маленьким собственным ожидаемым буфером ограничивает фактически принимаемую длину.

  8. После таймаута записи не повторяйте отправку вслепую. После закрытия формы поздний callback не подтверждает сохранение, повторное открытие формы не восстанавливает ownership-пакет автоматически.

  9. Сырые ошибки/ключи S3/пути/содержимое внешних файлов не журналируются. Образец не выполнялся и не является измерением доступности/квоты конкретного сервера.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.