Два собственных участника: подписка на сообщения через Ждать/callback и отдельная интерактивная отправка

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

Реальные ID двух вручную подготовленных пользователей своей новой базы, собственное обсуждение и настоящая отправка только между ними. Подключение/отключение выполняется двумя API-стилями; callback-копии, серверное сохранение и факт интерактивного действия проверяются отдельно.

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

Главный код — полный модуль своей управляемой формы; поддерживающий код — полный серверный общий модуль УчебныеОбработчики85. Только новая CF УчебныеОповещенияСВ85, ровно два своих UUID-пользователя и два собственных сеанса на localhost:9094.

Подготовка

  1. Нужна отдельная новая пустая CF УчебныеОповещенияСВ85 на8.5.1.1343: русский язык, управляемый интерфейс, совместимость Не использовать. Никаких предметных объектов, подписок, заданий, расширений, ботов и интеграций. Это другой стенд, не база урока одиночного пользователя.

  2. Вручную разверните свой выделенный сервер взаимодействия в изолированной VM с адресом http://localhost:9094, отдельным собственным абонентом и приложением УчебныеОповещенияСВ85. Без SMTP, публичного доступа, общих приложений, внешних мессенджеров или чужих пользователей. Серверный конфигурационный контракт берется из поставки сервера, здесь не придумывается.

  3. В конфигураторе вручную создайте ровно два своих пользователя: Уч85Получатель_<ваш новый канонический ненулевой UUID> и Уч85Отправитель_<другой ваш новый UUID>. Полные имена Учебный получатель/Учебный отправитель. UUID-маркер имени не является реальным IB UUID: реальные IB UUID код получает из фактических пользователей базы.

  4. Назначьте необходимые права и включите систему взаимодействия для этих двух учебных учетных записей только вручную на своем стенде. Код не создает аккаунты/права. Двусторонний GetID/IBUUID и свежие internal-user паспорта должны совпасть; почта/телефон/внешние ID/ключи сопоставления пустые.

  5. Войдите двумя отдельными сеансами этой своей базы: первый Получатель, второй Отправитель. Чужих сеансов нет. Оба ваши; разрешена отправка только между ними. Не используйте ВсеПользователиПриложения и не подменяйте ID строкой выдуманного UUID.

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

  7. Добавьте строковые реквизиты формы Подтверждение длиной100, СвойКлючОтправителя длиной100, ЭтапОтправителя длиной30, ПодтверждениеОтправителя длиной100. Добавьте команды ПодготовитьПолучателя, ПодключитьЖдать, ОтключитьЖдать, ПодключитьCallback, ОтключитьCallback, ПроверитьСохранение, ОтправитьИзВторогоСеанса и одноименные обработчики.

  8. В первом сеансе введите ДВА СВОИХ УЧАСТНИКА и нажмите ПодготовитьПолучателя один раз. Полученный собственный новый ключ скопируйте во второй сеанс в СвойКлючОтправителя. Во втором поле ПодтверждениеОтправителя задайте ОТПРАВИТЬ СВОЕМУ ПОЛУЧАТЕЛЮ. Без этих фраз записи нет.

  9. Нажмите ПодключитьЖдать в первом сеансе и дождитесь сообщения о завершении. Только затем во втором выберите ЭтапОтправителя=Ждать и нажмите ОтправитьИзВторогоСеанса один раз. Дождитесь собственного нового события в первом сеансе; не открывайте обсуждение до этого, чтобы UI не изменил статус прочтения.

  10. Нативные новые-message handlers работают thin/web/thick; серверная подготовка остается на сервере. Пример не использует противоречивый ПолучитьОбсуждениеАсинх и не требует переносить серверные IB-проверки на клиент. При необходимости одинаково проверяйте маршрут сначала в толстом управляемом клиенте.

  11. Интерактивную проверку выполняйте вручную в первом сеансе: в интерфейсе системы взаимодействия найдите только собственное обсуждение по выданному ключу/заголовку и отправьте точный текст <ключ>|Ручной|750. Программный Записать второго сеанса не обязан вызывать AfterMessageSend: native указывает именно интерактивную отправку.

  12. Перед закрытием формы сознательно нажмите соответствующее Отключить и дождитесь завершения. Автоматического подключения при открытии, отключения внутри события, повторов после таймаута и удаления сообщений нет. При неопределенном состоянии прекратите опыт и проверьте свой сеанс/сервер вручную; не считайте callback доказательством серверного отключения.

Перем СвойПакет85;
Перем НачатаПодготовка85;
Перем Режим85;
Перем Запрос85;
Перем СвойОбработчик85;
Перем ПослеОтправки85;
Перем Полученные85;
Перем Интерактивные85;
Перем Поздние85;

&НаКлиенте
Процедура ПодготовитьПолучателя(Команда)
    Если НачатаПодготовка85 = Истина Тогда
        ВызватьИсключение "Повтор запрещен: проверьте возможный частичный результат вручную";
    КонецЕсли;
    НачатаПодготовка85 = Истина;
    СвойПакет85 = ПодготовитьНаСервере85();
    Полученные85 = Новый Массив;
    Интерактивные85 = 0;
    Поздние85 = 0;
    Режим85 = "Отключено";
    Сообщить("Второму собственному сеансу передайте только этот ключ: " + СвойПакет85.Ключ);
КонецПроцедуры

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

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

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

&НаКлиенте
Процедура ПодключитьCallback(Команда)
    ПроверитьСвоегоПолучателя85("Отключено");
    ПодготовитьСвоиОписания85();
    Завершение = Новый ОписаниеОповещения("ПодключениеЗавершено85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
    Режим85 = "ПодключаетсяCallback";
    Попытка
        СистемаВзаимодействия.НачатьПодключениеОбработчикаНовыхСообщений(Завершение, СвойПакет85.Ключ, СвойОбработчик85, Неопределено);
    Исключение
        Режим85 = "НеопределенноеСостояниеCallback";
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

&НаКлиенте
Процедура ПодключениеЗавершено85(ДополнительныеПараметры) Экспорт
    Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Или Режим85 <> "ПодключаетсяCallback" Тогда
        Возврат;
    КонецЕсли;
    Попытка
        СистемаВзаимодействия.ПодключитьОбработчикПослеОтправкиСообщения(ПослеОтправки85);
        Режим85 = "Callback";
    Исключение
        Режим85 = "НеопределенноеСостояниеCallback";
        ВызватьИсключение;
    КонецПопытки;
    Сообщить("Callback подключения получен. Теперь отправьте этап Callback из второго собственного сеанса");
КонецПроцедуры

&НаКлиенте
Процедура ОтключитьCallback(Команда)
    ПроверитьСвоегоПолучателя85("Callback");
    Завершение = Новый ОписаниеОповещения("ОтключениеЗавершено85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
    Режим85 = "ОтключаетсяCallback";
    Попытка
        СистемаВзаимодействия.НачатьОтключениеОбработчикаНовыхСообщений(Завершение, СвойПакет85.Ключ, СвойОбработчик85);
    Исключение
        Режим85 = "НеопределенноеСостояниеCallback";
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

&НаКлиенте
Процедура ОтключениеЗавершено85(ДополнительныеПараметры) Экспорт
    Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Или Режим85 <> "ОтключаетсяCallback" Тогда
        Возврат;
    КонецЕсли;
    Попытка
        СистемаВзаимодействия.ОтключитьОбработчикПослеОтправкиСообщения(ПослеОтправки85);
        Режим85 = "Отключено";
    Исключение
        Режим85 = "НеопределенноеСостояниеCallback";
        ВызватьИсключение;
    КонецПопытки;
    Сообщить("Callback отключения завершен. Native-справка прямо не гарантирует отключение обработчика сервером");
КонецПроцедуры

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

&НаКлиенте
Процедура ПослеСвоейИнтерактивнойОтправки85(Сообщение, Обсуждение, ДополнительныеПараметры) Экспорт
    Если Не СвойТекущийЗапрос85(ДополнительныеПараметры) Тогда
        Возврат;
    КонецЕсли;
    Если ТипЗнч(Сообщение) <> Тип("СообщениеСистемыВзаимодействия")
        Или ТипЗнч(Обсуждение) <> Тип("ОбсуждениеСистемыВзаимодействия") Тогда
        Возврат;
    КонецЕсли;
    Если Строка(Обсуждение.Идентификатор) <> СвойПакет85.Обсуждение
        Или Строка(Сообщение.Обсуждение) <> СвойПакет85.Обсуждение
        Или Строка(Сообщение.Автор) <> СвойПакет85.Получатель Тогда
        Возврат;
    КонецЕсли;
    Если Режим85 <> "Ждать" И Режим85 <> "Callback" Тогда
        Возврат;
    КонецЕсли;
    Если Обсуждение.Ключ <> СвойПакет85.Ключ Или Сообщение.Текст <> СвойПакет85.Ключ + "|Ручной|750" Тогда
        Возврат;
    КонецЕсли;
    Интерактивные85 = Интерактивные85 + 1;
    Сообщить("Замечена своя интерактивная отправка. Проверяйте сохранение отдельно кнопкой ПроверитьСохранение");
КонецПроцедуры

&НаКлиенте
Процедура ОшибкаСвоегоОбработчика85(ИнформацияОбОшибке, ДополнительныеПараметры) Экспорт
    Если СвойТекущийЗапрос85(ДополнительныеПараметры) Тогда
        Если Режим85 = "Ждать" Или Режим85 = "ОтключаетсяЖдать" Тогда
            Режим85 = "НеопределенноеСостояниеЖдать";
        Иначе
            Режим85 = "НеопределенноеСостояниеCallback";
        КонецЕсли;
        Сообщить("Ошибка своего обработчика: состояние не подтверждено, сырые ошибки не выводятся, повторов нет");
    КонецЕсли;
КонецПроцедуры

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

&НаСервереБезКонтекста
Функция ПроверитьНаСервере85(Ключ)
    Возврат УчебныеОбработчики85.ПроверитьСохраненныеСообщения85(Ключ);
КонецФункции

&НаКлиенте
Процедура ОтправитьИзВторогоСеанса(Команда)
    ОтправитьНаСервере85(СвойКлючОтправителя, ЭтапОтправителя, ПодтверждениеОтправителя);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ОтправитьНаСервере85(Ключ, Этап, Фраза)
    УчебныеОбработчики85.ОтправитьСвоеСообщение85(Ключ, Этап, Фраза);
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьСвоегоПолучателя85(ОжидаемыйРежим)
    Если СвойПакет85 = Неопределено Или Режим85 <> ОжидаемыйРежим Тогда
        ВызватьИсключение "Неверный этап: не подключайте и не отключайте обработчик повторно";
    КонецЕсли;
    Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
        Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094"
        Или Строка(СистемаВзаимодействия.ИдентификаторТекущегоПользователя()) <> СвойПакет85.Получатель Тогда
        ВызватьИсключение "Свой сервер или получатель изменен";
    КонецЕсли;
    ПроверитьНаСервере85(СвойПакет85.Ключ);
КонецПроцедуры

&НаКлиенте
Процедура ПодготовитьСвоиОписания85()
    Запрос85 = Строка(Новый УникальныйИдентификатор);
    СвойОбработчик85 = Новый ОписаниеОповещения("СвоеНовоеСообщение85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
    ПослеОтправки85 = Новый ОписаниеОповещения("ПослеСвоейИнтерактивнойОтправки85", ЭтотОбъект, ПараметрыСвоегоЗапроса85(), "ОшибкаСвоегоОбработчика85", ЭтотОбъект);
КонецПроцедуры

&НаКлиенте
Функция ПараметрыСвоегоЗапроса85()
    Возврат Новый Структура("Запрос,Форма,Ключ", Запрос85, Строка(УникальныйИдентификатор), СвойПакет85.Ключ);
КонецФункции

&НаКлиенте
Функция СвойТекущийЗапрос85(Параметры)
    Если СвойПакет85 = Неопределено Тогда
        Возврат Ложь;
    КонецЕсли;
    Если Не СистемаВзаимодействия.ИнформационнаяБазаЗарегистрирована()
        Или СистемаВзаимодействия.АдресСервера() <> "http://localhost:9094"
        Или Строка(СистемаВзаимодействия.ИдентификаторТекущегоПользователя()) <> СвойПакет85.Получатель Тогда
        Возврат Ложь;
    КонецЕсли;
    Возврат Параметры.Запрос = Запрос85 И Параметры.Форма = Строка(УникальныйИдентификатор)
        И Параметры.Ключ = СвойПакет85.Ключ;
КонецФункции
Полный серверный общий модуль УчебныеОбработчики85: actual pair/own discussion/send/fresh reads

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

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

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

Процедура ОтправитьСвоеСообщение85(Ключ, Этап, Подтверждение) Экспорт
    Пара = ПроверитьСвоюПару85("Отправитель");
    Если Подтверждение <> "ОТПРАВИТЬ СВОЕМУ ПОЛУЧАТЕЛЮ"
        Или (Этап <> "Ждать" И Этап <> "Callback" И Этап <> "ПослеОтключения") Тогда
        ВызватьИсключение "Подтверждение или этап неверны. Сообщение не отправлено";
    КонецЕсли;
    Обсуждение = ПроверитьСвоеОбсуждение85(Ключ, Пара);
    Сообщение = СистемаВзаимодействия.СоздатьСообщение(Обсуждение.Идентификатор);
    Сообщение.Текст = Ключ + "|" + Этап + "|Книга|3|250|750";
    Сообщение.Данные = Ключ + "|" + Этап + "|750";
    Сообщение.ФорматДанныхСтроки = "text/plain";
    Сообщение.Получатели.Добавить(Пара.Получатель);
    Если Сообщение.Получатели.Количество() <> 1 Или Не Сообщение.Получатели.Содержит(Пара.Получатель)
        Или Сообщение.Вложения.Количество() <> 0 Или Сообщение.Действия.Количество() <> 0
        Или Сообщение.ПанельКнопок.РядыКнопок.Количество() <> 0 Тогда
        ВызватьИсключение "Состав собственной отправки неверен";
    КонецЕсли;
    ПроверитьСвоеОбсуждение85(Ключ, Пара);
    Сообщение.Записать();
    ИД = Сообщение.Идентификатор;
    Если ТипЗнч(ИД) <> Тип("ИдентификаторСообщенияСистемыВзаимодействия") Тогда
        ВызватьИсключение "Запись могла произойти, но ID не получен";
    КонецЕсли;
    Факт = СистемаВзаимодействия.ПолучитьСообщение(ИД);
    Если Факт.Идентификатор <> ИД Или Факт.Обсуждение <> Обсуждение.Идентификатор
        Или Факт.Автор <> Пара.Отправитель Или Факт.Текст <> Сообщение.Текст Или Факт.Данные <> Сообщение.Данные Тогда
        ВызватьИсключение "Собственная отправка отдельно не подтверждена";
    КонецЕсли;
    Сообщить("Сохранение собственного сообщения отдельно подтверждено; это еще не доказательство доставки callback получателю");
КонецПроцедуры

Функция ПроверитьСохраненныеСообщения85(Ключ) Экспорт
    Пара = ПроверитьСвоюПару85("Получатель");
    Обсуждение = ПроверитьСвоеОбсуждение85(Ключ, Пара);
    Отбор = Новый ОтборСообщенийСистемыВзаимодействия;
    Отбор.Обсуждение = Обсуждение.Идентификатор;
    Отбор.Количество = 50;
    Отбор.НаправлениеСортировки = НаправлениеСортировки.Возр;
    Сообщения = СистемаВзаимодействия.ПолучитьСообщения(Отбор);
    Если Сообщения.Количество() >= 50 Тогда
        ВызватьИсключение "Учебное обсуждение превысило лимит50; нужен новый свой опыт";
    КонецЕсли;
    Счетчики = Новый Структура("Ожидание,Callback,ПослеОтключения,Ручной", 0, 0, 0, 0);
    Для Каждого Сообщение Из Сообщения Цикл
        Если Сообщение.Обсуждение <> Обсуждение.Идентификатор Тогда
            ВызватьИсключение "Список содержит другое обсуждение";
        КонецЕсли;
        Для Каждого Счетчик Из Счетчики Цикл
            Этап = Счетчик.Ключ;
            Если Этап = "Ожидание" Тогда
                Этап = "Ждать";
            КонецЕсли;
            Если Счетчик.Ключ = "Ручной" Тогда
                Совпало = Сообщение.Автор = Пара.Получатель И Сообщение.Текст = Ключ + "|Ручной|750";
            Иначе
                Совпало = Сообщение.Автор = Пара.Отправитель
                    И Сообщение.Текст = Ключ + "|" + Этап + "|Книга|3|250|750"
                    И Сообщение.Данные = Ключ + "|" + Этап + "|750";
            КонецЕсли;
            Если Совпало Тогда
                Счетчики[Счетчик.Ключ] = Счетчики[Счетчик.Ключ] + 1;
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;
    Возврат Счетчики;
КонецФункции

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

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

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

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

  1. Проверка своей среды отвергает третью учетную запись, одинаковые роли, пустые/стандартные ID, внешние контакты, чужое приложение и адрес не localhost:9094. Реальные идентификаторы берутся только из двух реальных пользователей собственной базы.

  2. Получатель создает новый UUID-ключ и собственное отображаемое неконтекстное обсуждение ровно двух подтвержденных участников. Перед каждым серверным чтением/отправкой свежая копия проверяет exact key/title/состав/интеграцию/контекст.

  3. Для клиентской подписки создаются два разных ОписанияОповещения: новое сообщение и после интерактивной отправки. ДополнительныеПараметры содержит request UUID, form UUID и свой ключ; поздние события предыдущего опыта отбрасываются.

  4. ПодключитьЖдать ожидает обещание с результатом Неопределено, а не булево/объект сообщения. Только после завершения разрешен этап Ждать второго сеанса. СтатусОповещенияПользователя=Неопределено означает прямой вызов обработчика, не кликабельный popup.

  5. Отправитель проверяет своего текущего пользователя и обоих собственных участников заново. Новое сообщение содержит книгу3×250=750 и UUID-ключ, адресат ровно собственный получатель. Дата и Автор не устанавливаются привилегированно.

  6. После Записать отправитель получает настоящий ID и читает серверный результат отдельно. Это подтверждает сохранение, но не доставку клиентского события. Получатель проверяет тип/обсуждение/автора до чтения текста копии, затем exact этап/данные и уникальность real ID.

  7. ОтключитьЖдать использует тот же сохраненный объект ОписаниеОповещения, а не отключение всех чужих обработчиков. Ждать возвращает Неопределено. AfterMessageSend отключается отдельно. Отключение никогда не выполняется внутри обработчика сообщения.

  8. ПодключитьCallback начинает другой собственный request UUID. Завершение подключения получает ровно один параметр ДополнительныеПараметры; поток/булево/результат сюда не добавляются. После этого второй сеанс отправляет только этап Callback.

  9. ОтключитьCallback передает тот же зарегистрированный handler. Подпись завершения снова один параметр. Native-справка прямо говорит: этот callback вызывается как успешный даже если handler ранее не был подключен или сервер не выполнил отключение.

  10. Ручная отправка <ключ>|Ручной|750 в первом сеансе дает отдельное наблюдение AfterMessageSend(Сообщение,Обсуждение,ДополнительныеПараметры). Фильтр глобального handler отбрасывает чужие обсуждения/авторов, ничего из них не журналирует и не меняет.

  11. AfterMessageSend передает копию с локальной текущей датой и текущим автором, не доказательство серверной записи. Кнопка ПроверитьСохранение отдельно читает только свое подтвержденное обсуждение и считает точные сохраненные сообщения Ждать/Callback/ПослеОтключения/Ручной.

  12. После отключения во втором сеансе можно один раз отправить этап ПослеОтключения. ПроверитьСохранение должен показать его серверное сохранение; клиентское наблюдение/отсутствие callback оценивайте отдельно. Отсутствие за некоторое время не является гарантией от сервера.

  13. Сравнение handler native учитывает имя процедуры/модуль и обработчик ошибок/его модуль, но не ДополнительныеПараметры. Поэтому два API-стиля не подключены одновременно; переходы выполняются последовательно с одной сохраненной ссылкой handler.

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

  1. В обоих сеансах проверены реальные разные UUID-linked внутренние учетные записи; роли перепутаны или third user → отказ до отправки.

  2. Для своего этапа Ждать/Callback серверный счетчик >=1 и наблюдение уникального ID в правильном сеансе являются разными проверками. Поздние повторные копии с тем же ID не увеличивают количество уникальных новых сообщений.

  3. Интерактивный счетчик растет только для точного собственного обсуждения и текста <ключ>|Ручной|750; программная отправка не используется как доказательство интерактивного события.

  4. Все три штатных success callback имеют native arity: завершения1, new-message2, after-send3. Обещания подключения/отключения ожидаются и проверяются на Неопределено.

  5. ПослеОтключения отдельно подтверждается по серверной записи. Программа не обещает, что completion callback измерил факт отключения на сервере; конфликт наблюдений считается неопределенным состоянием.

  6. Пустая фраза, чужой ключ без собственного префикса, добавленный участник, внешний контекст/интеграция → отказ. Чужой текст/сырая ошибка/контакты никогда не попадают в сообщения формы.

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

При ручном запуске на своем двухпользовательском стенде получатель увидит собственные copy-events после реальных сообщений другого своего участника; серверные записи и интерактивная отправка проверяются отдельно. Оба API-стиля подписки используются полностью. Эти примеры статически сверены со справкой8.5.1.1343, реальные сеансы/отправка здесь не выполнялись.

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

  1. Не используйте fake UUID вместо actual ID: имя с UUID — только метка, связь берется из реального пользователя информационной базы и подтверждается в обе стороны.

  2. Два сеанса одного пользователя не являются двумя участниками. Этот полный workflow требует именно двух разных собственных учетных записей; он не подходит для одиночного профиля предыдущего урока.

  3. Подключение может сразу дать непрочитанные старые сообщения. Маркер/автор/этап/уникальный ID и request UUID отделяют такие события от новой проверки. Порядок нескольких handler не определен.

  4. ДополнительныеПараметры не участвуют в равенстве handler; новый request UUID не создает независимый native-handler с той же процедурой/модулем.

  5. В new-message передается копия. Не записывайте ее повторно, не ставьте Author/Date и не отключайте обработчик из его же тела; этот код только проверяет и считает собственные наблюдения.

  6. AfterMessageSend относится к интерактивной отправке и использует локальную дату; его наличие/ID не считается подтверждением сохранения. Проверяйте отдельное серверное чтение.

  7. Success callback BeginDetach вызывается даже без подтверждения действия сервером. Отсутствие наблюдения некоторое время не доказывает доставку/отключение: здесь не скрыта эта native граница.

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

  9. Лимит50 — учебный предел; для больших обсуждений нужна настоящая пагинация. В этом уроке нельзя молча брать первые50 и выдавать неполный список как полный.

  10. Не выполняйте автоматические повторы отправки или cleanup при ошибке. После закрытия формы нельзя считать клиентские callback/счетчики восстановленными; новые резервные копии не создаются.

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

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

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

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

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