Полный учебный маршрут создает реальное собственное обсуждение и сообщение с двумя UTF-8 вложениями. Работает с коллекцией как редактором нового сообщения, затем отдельно читает серверный результат и сравнивает байты через sync, Ждать и ОписаниеОповещения.
Где и как запускать
Полный модуль собственной управляемой формы, толстый клиент/управляемое приложение. Серверный поддерживающий модуль УчебныеВложения85 обязателен и приведен полностью. Каждый модуль вставляется отдельно в свое место, не склеивается.
Подготовка
Используйте отдельную VM, платформу8.5.1.1343 и новую пустую конфигурацию УчебноеВзаимодействие85: русский язык, управляемый интерфейс, совместимость Не использовать. Предметных объектов/регламентных заданий/подписок/расширений нет. Не запускайте в УНФ/КА/УТ/БП или на рабочем сервере.
Вручную разверните отдельный собственный сервер взаимодействия по документации своей серверной поставки. Единственный разрешенный адрес регистрации http://localhost:9094; никаких внешних мессенджеров, SMTP, общего абонента или публичной публикации. Адрес с другим хостом намеренно не принимается.
На этом же стенде вручную подключите внешнее файловое хранилище только с собственными учебными учетными данными и локальной конечной точкой localhost в этой VM. Создайте собственный пустой учебный бакет; не используйте рабочие S3-ключи/бакеты. Формат серверного конфигурационного файла зависит от отдельной поставки сервера и здесь не придумывается.
В справке ПоддерживаетсяРаботаСВложениями означает подключение файлового хранилища, а не его доступность по сети или достаточную квоту. Локальные разрешения/вместимость/срок хранения проверьте вручную; отключите короткое удаление только на своем пустом стенде. Без хранилища код отказывает до создания обсуждения.
В конфигураторе создайте ровно одного своего пользователя Уч85Связь_<ваш новый канонический ненулевой UUID>, полное имя Учебный участник. Права назначаются вручную только в собственном стенде. Войдите под ним; его реальный IB UUID будет сверяться в обе стороны с реальным ID системы взаимодействия.
Вручную зарегистрируйте свое приложение под именем УчебноеВзаимодействие85. Свой пользователь внутренний, не заблокирован, без внешнего ID/почты/телефона/ключа сопоставления. Он должен совпадать с реальным пользователем базы. Ни аккаунты, ни права этот образец не создает.
Создайте серверный общий модуль УчебныеВложения85, только флаг Сервер; Привилегированный выключен. Вставьте главный модуль полностью. Здесь включены проверка пустой среды, паспорт своего пользователя/приложения, создание данных и проверка потока.
Создайте свою внешнюю обработку с управляемой формой и реквизитом Подтверждение типа Строка длиной100. Добавьте команды Подготовить, ПрочитатьЖдать, ПрочитатьЧерезОповещение и назначьте одноименные обработчики. В модуль формы вставьте полный поддерживающий код. Установите реквизит формы Подтверждение в доступное поле.
Откройте форму в толстом клиенте, управляемое приложение. Сам AsyncOpenStream доступен также thin/web, но полный маршрут сознательно использует толстый клиент для синхронного GetMessage/получения обсуждения; серверный модуль не переносится на клиент.
Сначала оставьте Подтверждение пустым: подготовка должна завершиться исключением без создания обсуждения. Закройте и заново откройте форму; после прочтения предупреждений введите СВОИ ДВА ВЛОЖЕНИЯ и нажмите Подготовить один раз. Флаг повторного запуска выставляется до серверного вызова.
Оставьте только этот собственный сеанс и не меняйте созданную карточку параллельно. Вложения действительно передаются в свое файловое хранилище при ручном запуске. Метод Очистить показан только на отдельном новом неотправленном черновике, а не на сохраненном сообщении.
После ошибки не нажимайте подготовку повторно и не удаляйте данные автоматически. Сохраните показанный 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 Тогда
ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
КонецЕсли;
КонецПроцедурыРазбор по шагам
Сначала проверяется пустая конфигурация, отсутствие привилегированного режима и собственный real-user/real-application паспорт. Адрес localhost сам по себе не доказывает владельца сервера: VM и регистрацию проверяете вы.
После точной фразы и проверки поддержки файлов новый UUID-ключ используется только для собственного нового обсуждения. Перед записью сообщения свежая копия обсуждения проверяет единственного своего участника, отсутствие внешней интеграции и контекста.
Книга: 3 по 250 дает750. Карточка содержит свой ключ и этот расчет; второе двоичное вложение содержит собственную проверку750. UTF-8 без BOM и MIME передаются явно; расширение .bin не является доказательством содержимого.
Добавить не объявлен в native-справке как возвращающий вложение: результат не присваивается. Объект карточки получаем через Получить(0). Вставить возвращает новый настоящий объект проверки и ставит его в начало.
Третий собственный черновик нужен для реальной задачи редактирования списка. Последовательность: [проверка,карточка,черновик] → Переместить → [карточка,проверка,черновик] → Сдвинуть → [карточка,черновик,проверка] → Удалить(1) → [карточка,проверка].
После каждого этапа проверяются Количество/Индекс/Получить и точные имена своих элементов. Индексы начинаются с0. Удаляется только собственный лишний черновик нового сообщения, а не файл по произвольному пути.
Все исходные потоки остаются открыты до завершения Записать и закрываются как при успехе, так и при исключении. При ошибке записи неизвестен частичный результат: код не повторяет отправку и не делает автоматический откат.
ПолучитьСообщение читает реальный ID, полученный после записи. Сверяем автора, обсуждение, UUID-маркер, текст и количество вложений, затем каждый readonly паспорт: имя, MIME, размер в байтах, отображение, содержимое удалено.
Серверный OpenStreamForRead открывает сохраненное содержимое. Чтение порциями128 байт сравнивает только фактически прочитанный участок. Короткий ненулевой read не равен концу; EOF только0. Нельзя полагаться на Size/Seek у произвольного потока.
Для Очистить создается отдельный новый объект сообщения и новый поток черновика. После удаления всех его вложений проверяется0; Записать для этого черновика отсутствует. Возможные временные объекты хранилища зависят от серверной поставки.
Команда ПрочитатьЖдать повторно проверяет текущего пользователя/локальный адрес/паспорт сохраненной карточки. Ждать превращает обещание в Поток, затем тот же полный byte-check закрывает поток.
Команда ПрочитатьЧерезОповещение передает request UUID и form UUID. Подпись успеха ровно (ПотокТела,ДополнительныеПараметры); ошибка имеет (ИнформацияОбОшибке,ДополнительныеПараметры). Запоздалый чужой callback не обрабатывает карточку и закрывает полученный поток.
Как проверить результат
Без точного подтверждения/с правильным пользователем, но без файлового хранилища, обсуждение и сообщение не создаются.
У сохраненного собственного сообщения ровно два вложения: <ключ>-card.txt показано, <ключ>-check.bin скрыто. Скрытое тоже реально сохраняется; это не секретность/ограничение доступа.
Ни один readonly паспорт и readonly свойство Сообщение.Вложения не присваивается. Изменяется только содержимое коллекции своего нового объекта.
После успешного серверного чтения и каждой клиентской команды все байты собственного UUID-маркера/расчета750 совпадают, а потоки закрыты.
Измененное имя/MIME/размер/тело, добавленный участник, чужой автор или СодержимоеУдалено вызывают отказ. СодержимоеУдалено нельзя установить для имитации; это readonly состояние настоящего хранилища.
Возврат обещания или вызов callback сам по себе не доказывает корректность содержимого: положительный результат выдается только после точного чтения всех байтов до EOF.
Ожидаемый результат
При ручном успешном запуске на своем подготовленном стенде остается одно новое обсуждение с собственной карточкой книги и двумя проверенными вложениями; отдельный очищенный черновик не отправляется. Три API-стиля читают одинаковые реальные байты750. Здесь подтверждены исходный код/нативные сигнатуры и статические проверки, но runtime1С отсутствует.
На что обратить внимание
localhost не заменяет проверку выделенного сервера, абонента и локального бакета. Подключенное хранилище может быть недоступно; исключение не означает, что запись не произошла частично.
Вложения — readonly коллекция свойства сообщения, но ее методы изменяют состав нового сообщения. Нельзя присвоить новую коллекцию или создавать Вложение/Коллекция через неподтвержденный конструктор.
У Добавить нет документированного return, у Вставить есть ВложениеСистемыВзаимодействия. Не копируйте произвольные параметры из похожих коллекций.
Размер означает байты, не СтрДлина; UTF-8 кириллицы занимает несколько байтов. MIME/Отображаемое не проверяют и не защищают содержимое.
Удалить(индекс)/Очистить здесь только собственные новые объекты. Применение к чужому/сохраненному сообщению требует отдельного согласованного сценария и проверки прав; этот пример такого разрешения не дает.
Синхронный OpenStreamForRead отсутствует в thin/web. Async/callback API доступны клиенту, но весь пример с GetMessage ограничен толстым управляемым клиентом честно.
Поток не читается огромным неограниченным массивом и не сохраняется в файл. Сравнение с маленьким собственным ожидаемым буфером ограничивает фактически принимаемую длину.
После таймаута записи не повторяйте отправку вслепую. После закрытия формы поздний callback не подтверждает сохранение, повторное открытие формы не восстанавливает ownership-пакет автоматически.
Сырые ошибки/ключи S3/пути/содержимое внешних файлов не журналируются. Образец не выполнялся и не является измерением доступности/квоты конкретного сервера.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.