В статье использована информация из:
Статья на ИТС, Источник1, Источник2 и ещё из многих мест.
В каждом из источников содержалась часть информации, которая полезна в целом, но не была полноценной, а порой даже ошибочной (ну или я ошибочно думаю, что она ошибочна).
На ИТС есть явное ограничение: Доступно только для лицензии КОРП. Но я так и не смог понять, в чём оно выражается. Возможно оно моральное...
Так же, как я ни пытался, не смог заставить работать через ws, только через hs, при этом про возможность работы через hs на ИТС упоминаний не нашёл.
Указанные в ИТС ограничения, например, Вызов метода WebCommonInfoBases.GetInfoBases() должен выполнять с аутентификацией, тоже оказались необязательными.
В разработке, где собственно и вычитал про hs, информация для меня оказалась очень сумбурной, пришлось долго переваривать и вникать, а выгрузка конфигурации - не полной.
Надеюсь, я хоть как-то объяснил причину возникновения этой статьи.
Итак, решаемая задача.
В организации 20+ рабочих мест, 10+ баз 1С, которые требуется в том или ином списке подключать, отключать, заменять и т.д. пользователям.
Решение.
Использовать механизм получения баз, встроенный в платформу.
При наличии в файле 1cestart.cfg строчки InternetService=http://СерверПубликацииБаз/ИмяПубликации/hs, которая появится там после добавления этого сервиса в настройках, лончер будет пытаться получить список баз с сервера СерверПубликацииБаз и добавить его в общий.
Порядок его работы (информация взята из Источника1):
1) HEAD-запрос на адрес http://СерверПубликацииБаз/ИмяПубликации/hs/WebCommonInfoBases. Последнее подставляется платформой автоматически. На этот запрос должен прийти ответ 200, только в этом случае будет пункт 2.
2) GET-запрос на адрес http://СерверПубликацииБаз/ИмяПубликации/hs/WebCommonInfoBases. В этом запросе помимо прочего будут переданы параметры ClientID и InfoBasesCheckCode. От них, а так же от параметра Запрос.ОтносительныйURL будет строиться дальнейшая логика. На этот запрос должен прийти ответ 200 с заголовками Content-Type : application/json;charset=utf-8 и Cache-Control : no-cache и телом-JSON-строкой вида {"root":{"InfoBasesChanged":true,"URL":СервисПолученияСпискаv8i}}, где СервисПолученияСпискаv8i в моём случае тот же самый сервис, http://СерверПубликацииБаз/ИмяПубликации/hs/WebCommonInfoBases (хотя по ИТС должен быть другим и обязательно требовать авторизацию)
Если InfoBasesChanged = ЛОЖЬ, на этом всё и заканчивается, это означает, что менять список не нужно.
InfoBasesChanged = ИСТИНА, продолжаем.
3) HEAD-запрос на адрес СервисПолученияСпискаv8i (получен в п.2). На этот запрос должен прийти ответ 200, только в этом случае будет пункт 4.
4) GET-запрос на адрес СервисПолученияСпискаv8i (получен в п.2). На этот запрос должен прийти ответ 200 с заголовками Content-Type : application/json;charset=utf-8 и Cache-Control : no-cache и телом-JSON-строкой вида {"root":{"InfoBasesCheckCode":Версия,"ClientID":КодКлиента,"InfoBases":СтрокаСоСпискомБазВФорматеV8i}}
Вот, в общем то, и всё, что требуется из теории.
Практика.
Создаём http-сервис. Имя сервиса очень желательно латиницей, потому как его надо будет публиковать, а кириллица в публикации - зло. В моём случае WebCommonInfoBasesService. Важно! КорневойURL должен быть WebCommonInfoBases.
Создаём http-шаблон. Я назвал CheckNGetInfoBases. Сам шаблон /*. Т.е. он будет обрабатывать все запросы.
Создаём http-метод Head. Обработчик CheckNGetInfoBasesHead (ниже)
Создаём http-метод Get. Обработчик CheckNGetInfoBasesGet
Листинг модуля WebCommonInfoBasesService
Функция CheckNGetInfoBasesGet(Запрос)
ClientID = Запрос.ПараметрыЗапроса.Получить("ClientID");
Если ClientID = Неопределено Тогда
Возврат Новый HTTPСервисОтвет(404);
КонецЕсли;
InfoBasesCheckCode = Запрос.ПараметрыЗапроса.Получить("InfoBasesCheckCode");
Возврат ОтветНаЗапрос(Запрос, ClientID, InfoBasesCheckCode);
КонецФункции
Функция CheckNGetInfoBasesHead(Запрос)
Возврат Новый HTTPСервисОтвет(200);
КонецФункции
Функция ОтветНаЗапрос(Запрос, ClientID, InfoBasesCheckCode)
ДанныеОтвета = Новый Структура("root", "");
Хост = Запрос.Заголовки.Получить("Host");
Если Запрос.ОтносительныйURL = "/CheckInfoBases" Тогда
URL = Запрос.БазовыйURL;
Порт = "8083";
Upgrade = Запрос.Заголовки.Получить("Upgrade");
Если НЕ Upgrade = Неопределено Тогда
URL = СтрЗаменить(URL, Хост, Хост + ":" + Порт);
КонецЕсли;
Если ClientID = "00000000-0000-0000-0000-000000000000" Тогда
ДанныеОтвета.root = Новый Структура("InfoBasesChanged, URL", ИСТИНА, URL);
Иначе
Результат = ПолучитьСтруктуруВерсияСписокБазНужноМенять(ClientID, InfoBasesCheckCode);
ДанныеОтвета.root = Новый Структура("InfoBasesChanged, URL", Результат.InfoBasesChanged, URL);
РегистрыСведений.ИсторияПосещенияПользователей.ДобавитьИсторию(Результат.Пользователь, Хост, "Проверка");
КонецЕсли;
ИначеЕсли Запрос.ОтносительныйURL = "/GetInfoBases" Тогда
Результат = ПолучитьСтруктуруВерсияСписокБазНужноМенять(ClientID);
ДанныеОтвета.root = Новый Структура("ClientID, InfoBasesCheckCode, InfoBases", Результат.ClientID, Результат.InfoBasesCheckCode, Результат.InfoBases);
РегистрыСведений.ИсторияПосещенияПользователей.ДобавитьИсторию(Результат.Пользователь, Хост, "Обновление");
КонецЕсли;
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ДанныеОтвета);
Тело = ЗаписьJSON.Закрыть();
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки["Content-Type"] = "application/json;charset=utf-8";
Ответ.Заголовки["Cache-Control"] = "no-cache";
Ответ.УстановитьТелоИзСтроки(Тело);
Возврат Ответ;
КонецФункции
Функция ПолучитьСтруктуруВерсияСписокБазНужноМенять(ClientID, InfoBasesCheckCode = Неопределено)
Результат = Новый Структура;
Если InfoBasesCheckCode = Неопределено Тогда
//Это запрос на обновление списка, проверяем версию.
Пользователь = Справочники.Пользователи.НайтиПоКоду(ClientID);
Если НЕ ЗначениеЗаполнено(Пользователь) Тогда
Пользователь = ЗарегистрироватьПользователя();
КонецЕсли;
Результат.Вставить("InfoBasesCheckCode", Пользователь.Группа.Версия);
Результат.Вставить("ClientID", Пользователь.Код);
Результат.Вставить("InfoBases", ПолучитьСписокБаз(Пользователь));
Иначе
//Это запрос на проверку версии
Результат.Вставить("InfoBasesChanged", ИСТИНА);
Пользователь = Справочники.Пользователи.НайтиПоКоду(ClientID);
Если ЗначениеЗаполнено(Пользователь) И InfoBasesCheckCode = Пользователь.Группа.Версия Тогда
Результат.Вставить("InfoBasesChanged", ЛОЖЬ);
КонецЕсли;
КонецЕсли;
Результат.Вставить("Пользователь", Пользователь);
Возврат Результат;
КонецФункции
Функция ПолучитьСписокБаз(Пользователь);
Результат = "";
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ГруппыБазШаблоны.Шаблон.Текст КАК Текст
|ИЗ
| Справочник.ГруппыБаз.Шаблоны КАК ГруппыБазШаблоны
|ГДЕ
| ГруппыБазШаблоны.Ссылка = &Группа
| И НЕ ГруппыБазШаблоны.Ссылка.ПометкаУдаления
| И НЕ ГруппыБазШаблоны.Шаблон.ПометкаУдаления";
Запрос.УстановитьПараметр("Группа", Пользователь.Группа);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Результат = Результат + ВыборкаДетальныеЗаписи.Текст + Символы.ПС;
КонецЦикла;
Если Результат = "" Тогда
Результат = Справочники.ШаблоныБаз.НовыйПользователь.Текст;
КонецЕсли;
Результат = СтрЗаменить(Результат, "%КодПользователя%", Пользователь.Код);
Возврат Результат;
КонецФункции
Функция ЗарегистрироватьПользователя()
Пользователь = Справочники.Пользователи.СоздатьЭлемент();
Пользователь.Код = "" + Новый УникальныйИдентификатор();
Пользователь.Наименование = Пользователь.Код;
Пользователь.Группа = Справочники.ГруппыБаз.НовыеПользователи;
Пользователь.Записать();
Возврат Пользователь.Ссылка;
КонецФункции
Дополнительные объекты конфигурации (взято из Источника2)
Развёрнутое дерево конфигурации
Листинг модуля объекта ШаблоныБаз
Процедура ПриЗаписи(Отказ)
ПоменятьВерсиюГрупп();
КонецПроцедуры
Процедура ПередУдалением(Отказ)
ПоменятьВерсиюГрупп();
КонецПроцедуры
Процедура ПоменятьВерсиюГрупп()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ГруппыБазШаблоны.Ссылка
|ИЗ
| Справочник.ГруппыБаз.Шаблоны КАК ГруппыБазШаблоны
|ГДЕ
| ГруппыБазШаблоны.Шаблон = &Шаблон";
Запрос.УстановитьПараметр("Шаблон", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
СпрОбъект = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
СпрОбъект.Версия = "" + Новый УникальныйИдентификатор();
СпрОбъект.Записать();
КонецЦикла;
КонецПроцедуры
Листинг модуля менеджера ИсторияПосещенияПользователей
Процедура ДобавитьИсторию(Пользователь, АдресЗапроса, Причина) Экспорт
МенеджерЗаписи = РегистрыСведений.ИсторияПосещенияПользователей.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = ТекущаяДата();
МенеджерЗаписи.Пользователь = Пользователь;
МенеджерЗаписи.АдресЗапроса = АдресЗапроса;
МенеджерЗаписи.Причина = Причина;
МенеджерЗаписи.Записать(ИСТИНА);
КонецПроцедуры
Листинги текстов шаблонов НовыйПользователь и Тестовая база
[!Для получения списка баз свяжитесь с Администратором]
Connect=%КодПользователя%
ID=
App=Auto
WA=0
[1. Локальная база 3]
Connect=Srvr="1.1.0.10";Ref="base3";
ID=
App=Auto
WA=0
В конфигурацию также добавлен пользователь ib и роль для него. Роли даны права на чтение и запись объектов.
При публикации этот пользователь явно прописывается в vrd, чтобы обеспечить анонимные запросы (тоже из Источника2).
Какое-то время будет доступен сервер http://alg-rt.shmg.ru:8083/DistrIb/hs, который можно прописать в лончер и получить "заглушку" - обратитесь к Администратору.
Тестировал на платформе 8.3.9.2309 и 8.3.24.1342. Вероятно, между ними тоже всё нормально.