gifts2017

Реализация чата на 1С 8,3 (Управляемые формы)

Опубликовал Алексей _ (iolko) в раздел Программирование - Практика программирования

Детальное описание реализации небольшого чата для пользователей.
                Возможности:
                - Сортировка пользователей по статусу
                - Количество новых сообщений
                - Раскраска окна сообщений
                - Все на управляемых формах
                - RSA шифрование
Используемые источники:
http://infostart.ru/
http://smbsec.ru/1cpredpriyatie/primer-assimetrichnogo-shifrovaniyarsa-1spredpriyatie-8.html

И так приступим…. Вся реализация будет описана на чистой конфигурации.

                Первоначально научим 1С работать с пользователями.

Для этого нам понадобится:

- «Справочник.Пользователи»

(Код, Наименование (100), ПолноеНаименование - Строка 150,ИдентификаторПользователяИБ - Уникальный идентификатор)

Параметр сеанса - «ТекущийПользователь»  (Справочник ссылка пользователи)

После добавления данных объектов конфигурации, создадим общий модуль - «Работа с пользователями».


Добавим в него следующий код со вспомогательными процедурами и функциями.

//*********************************************************************************

Процедура ОпределитьТекущегоПользователя () Экспорт
	Если ПользователиИнформационнойБазы.ПолучитьПользователей().Количество()=0
		Тогда
		ТекстСообщенияОбОшибке = "Пользователи информационной базы не определены!";
		ВызватьИсключение ТекстСообщенияОбОшибке;
	Иначе
		ТекущийПользовательИБ 		= ПользователиИнформационнойБазы.ТекущийПользователь();
		ИдентификаторПользователяИБ = ТекущийПользовательИБ.УникальныйИдентификатор;
		
		ТекущийПользователь = Справочники.Пользователи.НайтиПоРеквизиту(
		"ИдентификаторПользователяИБ",ИдентификаторПользователяИБ);
		Если ТекущийПользователь.Пустая() Тогда
			НовыйПользователь = Справочники.Пользователи.СоздатьЭлемент();
			НовыйПользователь.ИдентификаторПользователяИБ = ИдентификаторПользователяИБ;
			НовыйПользователь.Наименование				  = ТекущийПользовательИБ.Имя;
			НовыйПользователь.ПолноеНаименование		  = ТекущийПользовательИБ.ПолноеИмя;
			НовыйПользователь.Записать();
			ТекущийПользователь = НовыйПользователь.Ссылка;
		КонецЕсли;
		ПараметрыСеанса.ТекущийПользователь = ТекущийПользователь;
	КонецЕсли;	
КонецПроцедуры
//*********************************************************************************

//*********************************************************************************
Функция ТекущийПользователь () Экспорт
	Возврат ПараметрыСеанса.ТекущийПользователь;
КонецФункции	
//*********************************************************************************

//*********************************************************************************
Функция ПолучитьПользователейИБ (ТолькоИдентификаторы) Экспорт
	Если ТолькоИдентификаторы = 1 Тогда	
		Массив = новый Массив;
		Массив = ПользователиИнформационнойБазы.ПолучитьПользователей();	
		МассивИдентификаторв = Новый Массив;
		Для Каждого ЭлементМассива Из Массив Цикл
			МассивИдентификаторв.Добавить(ЭлементМассива.УникальныйИдентификатор);	
		КонецЦикла;
		Возврат МассивИдентификаторв;
	Иначе  
		Возврат  ПользователиИнформационнойБазы.ПолучитьПользователей();
	КонецЕсли;	
КонецФункции
//*********************************************************************************

//*********************************************************************************
Функция ВернутьПользователйИзСправочника () Экспорт
	Массив = Новый Массив;
	Массив = ПолучитьПользователейИБ (1);
	Запрос = Новый Запрос;
	Запрос.Текст =  "ВЫБРАТЬ
	|	Пользователи.Ссылка,
	|	Пользователи.ИдентификаторПользователяИБ
	|ИЗ
	|	Справочник.Пользователи КАК Пользователи
	|ГДЕ
	|	Пользователи.ИдентификаторПользователяИБ В(&Пользователи)
	|
	|УПОРЯДОЧИТЬ ПО
	|	Пользователи.Наименование";
	
	Запрос.УстановитьПараметр("Пользователи",Массив);
	Выборка =  Запрос.Выполнить().Выбрать();
	МассивПользователейИзСпр = Новый Массив;
	Пока Выборка.Следующий() Цикл
		Стр = Новый Структура ("Пользователь, УИД");
		Стр.Вставить("Пользователь",Выборка.Ссылка);
		Стр.Вставить("УИД",Выборка.ИдентификаторПользователяИБ);
		МассивПользователейИзСпр.Добавить(Стр);
	КонецЦикла;
	Возврат МассивПользователейИзСпр;
КонецФункции
//*********************************************************************************

//*********************************************************************************
Функция ВернутьСтатусПользователя(УИДПользователяИБ) Экспорт
	Массив = Новый Массив;
	Для Каждого Элемент ИЗ ПолучитьСоединенияИнформационнойБазы() Цикл
		Массив.Добавить(Элемент.Пользователь.УникальныйИдентификатор);	
	КонецЦикла;
	
	Если Массив.Найти(УИДПользователяИБ)<> Неопределено Тогда
		Возврат 1
	Иначе
		Возврат 2
	КонецЕсли;
КонецФункции
//*********************************************************************************

Создадим регистр сведений для сообщений


«Отправитель, Адресат» - СправочникСсылка.Пользователи

«Текст» - Строка

«Прочтено» - Булево



Так же создадим две строковые константы для хранения ключей шифрования:



Обе константы строковые неограниченно длинны.

Создадим еще один общий модуль для работы с RSAшифрованием.


Код модуля

//********************************************************************************************
//http://smbsec.ru/1cpredpriyatie/primer-assimetrichnogo-shifrovaniyarsa-1spredpriyatie-8.html
//Внимание! Приведенная информация распространяется исключительно в ознакомительных целях.
//Перед использованием данных материалов рекомендуется ознакомиться с ФЗ Российской Федерации
//от 4 мая 2011 г. N 99-ФЗ "О лицензировании отдельных видов деятельности".
//Обработка является примером работы с RSA из 1С:Предприятие 
//и выполняет для примера шифрование и расшифрование текста.
//********************************************************************************************


Функция СтрокаВДвоичныйВид(ВходнаяСтрока) Экспорт
	Поток = Новый COMОбъект("ADODB.Stream");
	Поток.Open();
	Поток.Type = 2;
	Поток.Charset = "utf-8";
	Поток.WriteText(ВходнаяСтрока);
	
	Поток.Position = 0;
	Поток.Type = 1;
	
	ДвоичныеДанные = Поток.Read(-1);
	
	Поток.Close();
	Поток = Неопределено;
	Возврат ДвоичныеДанные;
КонецФункции

Функция ДвоичныеДанныеВстроку(ДвоичныеДанные) Экспорт
	Поток = Новый COMОбъект("ADODB.Stream");
	Поток.Open();
	Поток.Type = 1;
	
	Поток.Write(ДвоичныеДанные);
	Поток.Position = 0;
	Поток.Type = 2;
	Поток.Charset = "utf-8";
	ВыхСтр = Поток.ReadText(-1);
	Поток.Close();
	Поток = Неопределено;
	Возврат ВыхСтр;
КонецФункции

Функция ДвоичныеДанныеВBase64(bin) Экспорт
	ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
	Поток = Новый COMОбъект("ADODB.Stream");
	Поток.Open();
	Поток.Type = 1;
	Поток.Write(bin);
	Поток.SaveToFile(ИмяВременногоФайла, 1);
	Поток.Close();
	Поток = Неопределено;
	ДвоичныеДанные = Новый ДвоичныеДанные(ИмяВременногоФайла);
	УдалитьФайлы(ИмяВременногоФайла);
	Возврат Base64Строка(ДвоичныеДанные);
КонецФункции

Функция Base64ВДвоичныеДанные(ВхСтрока) Экспорт
	
	ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
	ДвоичныеДанные = Base64Значение(ВхСтрока);
	ДвоичныеДанные.Записать(ИмяВременногоФайла);
	
	Поток = Новый COMОбъект("ADODB.Stream");
	Поток.Open();
	Поток.Type = 1;
	Поток.LoadFromFile(ИмяВременногоФайла);
	ДвоичныеДанные = Поток.Read(-1);
	Поток.Close();
	Поток = Неопределено;
	
	УдалитьФайлы(ИмяВременногоФайла);
	Возврат ДвоичныеДанные;
	
КонецФункции

Функция Зашифровать(СтрокаДляШифрования)  Экспорт
	ОбъектШифрования = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	//Инициализация объект RSA, используя данные ключа из строки XML.
	//http://msdn.microsoft.com/ru-ru/library/system.security.cryptography.rsa.fromxmlstring.aspx
	ОбъектШифрования.FromXmlString(Константы.RSA_ПубличныйКлюч.Получить());
	//преобразование строки для шифрования в двоичный вид
	ИсхТекстДвоичн = СтрокаВДвоичныйВид(СтрокаДляШифрования);
	//шифруем
	ЗашифрованныйТекстДвоичн = ОбъектШифрования.Encrypt(ИсхТекстДвоичн, False);
	//преобразование зашифрованных данных в Base64
	ЗашифрованныйТекстСтр = ДвоичныеДанныеВBase64(ЗашифрованныйТекстДвоичн);
	Возврат ЗашифрованныйТекстСтр;
КонецФункции

Функция Расшифровать(ЗашифрованнаяСтрока) Экспорт
	ОбъектШифрования = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	ОбъектШифрования.FromXmlString(Константы.RSA_ПубличныйИПриватныйКлюч.Получить());
	ЗашифрованныйТекстДвоичн2 = Base64ВДвоичныеДанные(ЗашифрованнаяСтрока);
	РасшифрованныйТекстДвоичн = ОбъектШифрования.Decrypt(ЗашифрованныйТекстДвоичн2, False);
	//преобразование расшифрованных данных в строку
	РасшифрованныйТекстСтр = ДвоичныеДанныеВстроку(РасшифрованныйТекстДвоичн);
	Возврат  РасшифрованныйТекстСтр;	
	ОбъектШифрования = Неопределено;
КонецФункции

Процедура ПроверитьRSAКлючи()Экспорт
//генерируем новый приватный и публичный ключи
//в рабочей версии ключи требуется раздать пользователям и хранить
Если (Константы.RSA_ПубличныйИПриватныйКлюч.Получить()="") И (Константы.RSA_ПубличныйКлюч.Получить()="") Тогда
RSA_Шифрование.RSAСоздатьКлючи("","");		
КонецЕсли; 
КонецПроцедуры

Процедура RSAСоздатьКлючи(КлючШифрования, КлючШифрованияИРасшифрования) Экспорт
	//http://msdn.microsoft.com/ru-ru/library/system.security.cryptography.rsa.toxmlstring.aspx	
	ОбъектКриптографии				= Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	КлючШифрования					= ОбъектКриптографии.ToXmlString(False);
	КлючШифрованияИРасшифрования 	= ОбъектКриптографии.ToXmlString(True);
	Константы.RSA_ПубличныйИПриватныйКлюч.Установить(КлючШифрованияИРасшифрования);
	Константы.RSA_ПубличныйКлюч.Установить(КлючШифрования);
	ОбъектКриптографии 				= Неопределено;
КонецПроцедуры




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




Модуль формы

//*********************************************************************************
&НаКлиенте
Процедура ПриОткрытии(Отказ)
	Обновить ();
	RSA_Шифрование.ПроверитьRSAКлючи();
	ПодключитьОбработчикОжидания ("Обновить",10);
КонецПроцедуры
//*********************************************************************************

//*********************************************************************************
&НаКлиенте
Процедура Обновить ()
	Тз.Очистить();	
	Массив = Новый Массив;
	Массив = РаботаСПользователями.ВернутьПользователйИзСправочника();

	МассивСообщений = Новый Массив;
	МассивСообщений = ВернутьНепрочитанныеСообщения (РаботаСПользователями.ТекущийПользователь());
	Для Каждого ЭлементМассива ИЗ Массив Цикл
		Если ЭлементМассива.Пользователь = РаботаСПользователями.ТекущийПользователь() Тогда
			Продолжить
		КонецЕсли;	
		СтрТЗ = ТЗ.Добавить();	
		СтрТз.Пользователь = ЭлементМассива.Пользователь;
		Статус = РаботаСПользователями.ВернутьСтатусПользователя(ЭлементМассива.УИД);
		
		Если Статус = 1 Тогда
			СтрТЗ.Статус = "OnLine"; 
		Иначе
			СтрТЗ.Статус = "OffLine";
		КонецЕсли;
		
		Для Каждого Сообщение ИЗ МассивСообщений Цикл
			Если Сообщение.Отправитель = ЭлементМассива.Пользователь Тогда
				СтрТз.Сообщения = Сообщение.Количество;
			КонецЕсли;	
		КонецЦикла
	КонецЦикла;
	ТЗ.Сортировать("Статус Убыв, Пользователь Возр");
КонецПроцедуры
//*********************************************************************************

//*********************************************************************************
&НаСервере
Функция ВернутьНепрочитанныеСообщения (Получатель)
	Запрос = Новый Запрос;
	Запрос.Текст =   "ВЫБРАТЬ
	|	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Сообщения.Период) КАК Количество,
	|	Сообщения.Отправитель
	|ИЗ
	|	РегистрСведений.Сообщения КАК Сообщения
	|ГДЕ
	|	Сообщения.Адресат = &amp;Адресат
	|	И Сообщения.Прочтено = ЛОЖЬ
	|
	|СГРУППИРОВАТЬ ПО
	|	Сообщения.Отправитель" ;
	Запрос.УстановитьПараметр("Адресат",Получатель);
	Выборка = Запрос.Выполнить().Выбрать();
	МассивСообщений = Новый Массив;
	Пока Выборка.Следующий() Цикл
		Ст = Новый Структура ("Отправитель,Количество");
		Ст.Вставить("Отправитель",Выборка.Отправитель);
		Ст.Вставить("Количество",Выборка.Количество);
		МассивСообщений.Добавить(Ст);
	КонецЦикла;
	Возврат МассивСообщений;	 
КонецФункции
//*********************************************************************************

//*********************************************************************************
&НаКлиенте
Процедура ТЗВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
	// Вставить содержимое обработчика.
	СтандартнаяОбработка = Ложь;
	Адресат = Элементы.ТЗ.ТекущиеДанные.Пользователь;
	П = Новый Структура ("Адресат",Адресат);
	ФормаСообщений = ОткрытьФормуМодально ("Обработка.Чат.Форма.ФормаОтправкиСообщения",П);
КонецПроцедуры




На данной форме поле сообщения - это поле HTMLдокумента, для того чтобы раскрасить наш чат.


Модуль формы:

//*********************************************************************************
&НаКлиенте
Процедура Ответить(Команда)
	// Вставить содержимое обработчика.
	Если  
		ПроверитьЗаполнение()Тогда	
		ТекПериод 		= ТекущаяДата ();
		ТекАдресат 		= Адресат;
		ТекОтправитель 	= РаботаСПользователями.ТекущийПользователь();
		ТекТекст 		= Ответ;
		ТекСтатус		= Ложь;
		Д = Новый Структура ("Период,Адресат,Отправитель,Текст,Прочтено",ТекПериод,ТекАдресат,ТекОтправитель,ТекТекст,ТекСтатус);
		ЗаписатьОтвет (Д);
		Закрыть();
	КонецЕсли
	
КонецПроцедуры
//*********************************************************************************

//*********************************************************************************
&НаСервере
Процедура ЗаписатьОтвет (ДанныеДляЗаполнения)
	МенеджерЗаписей  = РегистрыСведений.Сообщения.СоздатьМенеджерЗаписи();
	МенеджерЗаписей.Период = ДанныеДляЗаполнения.Период;
	МенеджерЗаписей.Адресат = ДанныеДляЗаполнения.Адресат;
	МенеджерЗаписей.Отправитель = ДанныеДляЗаполнения.Отправитель;
	МенеджерЗаписей.Текст = RSA_Шифрование.Зашифровать(ДанныеДляЗаполнения.Текст);
	МенеджерЗаписей.Прочтено = Ложь;
	МенеджерЗаписей.Записать();
КонецПроцедуры
//*********************************************************************************

//*********************************************************************************
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	//Для того чтобы получить логическую цепочку сообщений 	
	//Заполним массив отправитель адресат
	Адресат = Параметры.Адресат;
	Массив = Новый Массив;
	Массив.Добавить(Адресат);
	Массив.Добавить(РаботаСпользователями.ТекущийПользователь());
	
	Запрос = Новый Запрос;
	Запрос.Текст =     "ВЫБРАТЬ ПЕРВЫЕ 10
	|	Сообщения.Текст,
	|	Сообщения.Период КАК Период,
	|	Сообщения.Отправитель,
	|	Сообщения.Адресат
	|ИЗ
	|	РегистрСведений.Сообщения КАК Сообщения
	|ГДЕ
	|	Сообщения.Отправитель В (&amp;Отправитель)
	|	И Сообщения.Адресат В (&amp;Адресат)
	|
	|УПОРЯДОЧИТЬ ПО
	|	Период Возр";
	
	Запрос.УстановитьПараметр("Отправитель",Массив);
	Запрос.УстановитьПараметр("Адресат",Массив);
	Выборка = Запрос.Выполнить().Выбрать();
	
	Текст = Новый ТекстовыйДокумент;
	Текст.ДобавитьСтроку("<!DOCTYPE html> <html>");
	
	Пока Выборка.Следующий() Цикл
		МенеджерЗаписи = РегистрыСведений.Сообщения.СоздатьМенеджерЗаписи();
		ЗаполнитьЗначенияСвойств(МенеджерЗаписи,Выборка);
		МенеджерЗаписи.Прочитать();
		Если МенеджерЗаписи.Выбран() Тогда
			МенеджерЗаписи.Прочтено = Истина;
			МенеджерЗаписи.Записать();
		КонецЕсли; 
		
		Если Выборка.Отправитель = РаботаСПользователями.ТекущийПользователь() Тогда
			Стр = "<font size=""3"" color=""red"" face=""Calibri"">""" +Строка(Выборка.Отправитель)+" "+Строка(Выборка.Период)+"</font>" + "<br>";
			Текст.ДобавитьСтроку(Стр);
			Стр = "<font size=""3"" color=""Black"" face=""Calibri"">""" +RSA_Шифрование.Расшифровать(Строка(Выборка.Текст))+"</font>" + "<br>";
			Текст.ДобавитьСтроку(Стр);
		Иначе 
			Стр = "<font size=""3"" color=""Blue"" face=""Calibri"">""" +Строка(Выборка.Отправитель)+" "+Строка(Выборка.Период)+"</font>" + "<br>";
			Текст.ДобавитьСтроку(Стр);
			Стр = "<font size=""3"" color=""Black"" face=""Calibri"">""" +RSA_Шифрование.Расшифровать(Строка(Выборка.Текст))+"</font>" + "<br>";
			Текст.ДобавитьСтроку(Стр);
		КонецЕсли; 	
	КонецЦикла;
	
	Текст.ДобавитьСтроку("</html>");
	ИмяВременногоФайла = ПолучитьИмяВременногоФайла("html");
	Текст.Записать(ИмяВременногоФайла);
	Сообщения = ИмяВременногоФайла;
	
КонецПроцедуры
//*********************************************************************************


Получается примерно следующее:



Регистр с сообщениями выглядит:



Как видно из скринов, сообщения зашифрованы нашими RSA Ключами.





См. также

Подписаться Добавить вознаграждение

Комментарии

1. Сергей (seermak) 09.09.14 12:10
Интересно, но - если ключей еще нет то после создания ключей продолжение процедуры дает ошибку в этой строке("ОбъектШифрования.FromXmlString(Константы.RSA_ПубличныйКлюч.Получить());" Еще перепутаны в форме с ТЗ (пользователи) = Адресат = Элементы.Пользователи.ТекущиеДанные; - здесь напутано в именах
2. Алексей _ (iolko) 10.09.14 02:41
(1) seermak, Доброго дня, действительно были недочеты, спасибо, что вовремя указали. Исправил, внес корректировки в код. Отправил на модерацию. Так же добавил скрины того что в итоге получается.
3. Александр Кузин (sashocq) 18.09.14 10:15
Выглядит как-то сыровато. Сделать только простенький чат — дело не хитрое. Тут, конечно, фишка в шифровании. Но в данной реализации оно выглядит неуместным. У нас рядом в базе лежит регистр с зашифрованными сообщениями и тут же в константах открытый и закрытый ключи (секретностью тут не пахнет). Если бы обмен сообщениями осуществлялся между разными базами — это можно было бы понять, а так нет. Если вы хотите сделать чат внутри одной базы между разными пользователями с шифрованием, то у каждого пользователя должен быть свой закрытый ключ, который не передается на сервер.
К тому же, в платформе есть механизмы для работы с криптографией. Хотелось бы реализации с их использованием. Ну, или как-то прокомментировать, почему нет (в статье про это ни слова).
Итогом, если статью назвать «как шифровать в 1С через COM», то пойдет, а с текущим заголовком — не зачет.
4. Сергей (seermak) 18.09.14 17:03
(3) да дело здесь скорее не в самом чате - + за то, что подвинул на изучение криптографии на таком простом примере))
5. Евгений Курилов (DragEugen) 24.09.14 14:15
спасибо сподвигли на изучение криптографии в 1с
6. haz haz (hazd) 05.10.14 23:57
что-то подобное на данном ресурсе уже встречалось,
7. Максим Литвинов (maksa2005) 23.10.14 08:32
Особено актуально, когда на работе запрещено устанавливать icq
8. Дмитрий Воронцов (informa1555) 01.11.14 08:07
Интересная статья, но визуально выглядит не очень. Я делал на СКД полностью чат вот в этой разработке http://infostart.ru/public/309075/
9. Сергей (lampa24111986) 20.09.16 13:38
{ОбщийМодуль.RSA_Шифрование.Модуль(80)}: Ошибка при вызове метода контекста (FromXmlString)
ОбъектШифрования.FromXmlString(Константы.RSA_ПубличныйКлюч.Получить());
по причине:
Произошла исключительная ситуация (mscorlib): Недопустимый синтаксис в строке 1.
10. Сергей (lampa24111986) 20.09.16 14:19
Странно, всё сделал. В обработке главную форму ставлю - "Форма"
Затем когда открываю обработку, в колонке "Пользователь" выбираю пользователя, которому хочу отправить сообщение и ничего не происходит, вторая форма не открывается, чтоб отправить сообщение.
11. Алексей _ (iolko) 21.09.16 12:23
(10) lampa24111986, Доброго дня. Если честно, чтобы понять в чем проблема нужно смотреть проблему целиком. Так ответить не готов. Вроде бы где то оставались исходники, если нужно могу выслать.
12. Сергей (lampa24111986) 21.09.16 17:13
(11) iolko, Если Вам не сложно, то хотелось бы их получить. Вот моя почта lampa241186@rambler.ru
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа