В этой статье я расскажу про систему диалогов на php, которую я создал на одном из сервисов компании, где я работаю.
Изначально, хотелось получить систему, которая бы повторяла функционал диалоговой системы контакта. Требования были следующие: скрипт не должен требовать установки на сервер дополнительных средств (поддержка long-pool запросов, phpdemon, поддержки websocket и т.п), позволять создавать диалог неограниченного числа пользователей и работать на обычном ajax-post сообщении с сайтом.
Приступим
Для начала создадим структуру БД
Вот дамп
Когда один пользователь хочет написать сообщение другому, скрипт ищет подходящий диалог. Диалог, является подходящим, если в нем участвую те же лица. Т.е. если мы раньше писали этому человеку, и никого больше не подключали к этому диалогу, то он подходит. Если же подходящего диалога нет, то создаем новый. Диалог и пользователь связаны таблицей user_to_dialog. Когда пользователь посылает сообщение, оно записывается в табличку message. А информация о том, кому оно предназначено лежит в табличке message_to_user. По сути, эта таблица избыточна, так как у нас уже есть информация кому показывать сообщение исходя из данных user_to_dialog. Но мне было так удобно. Можете это изменить в своем форке.
Перейдем к коду.
Структура класса dialog
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php class dialog{ public $utime = 0; // время по часовому поясу пользователя в UNIXTIME public $userid = 0; // id пользователя public $err = '' ; public $hash = '' ; // hash диалога public $id = '' ; private $user_id_field = 'IDClient' ; // название поля с id пользователя, необходимо для подключения //к Вашему скрипту с уже созданной структурой пользователей private function create(){} // создание нового диалога public function find_suit_dialog( $userlist = array ()){} // поиск подходящего диалога function get_new_messages_cnt(){} // количество новых сообщений для пользователя function get_users_from_dialog(){} // список пользователей принадлежащих диалогу function get_user_dialogs( $start =0, $cnt = 10 ){} // вывод диалогов пользователя function get_messages_from_dialog( $new =false, $reset_status = true){} // вывод сообщений из диалога function remove_users_from_dialog( $userlist = array () ){} function add_users_to_dialog( $userlist = array () ){} // добавить пользователя в диалог function send( $msg , $intro = false){} // посылка сообщения в диалог function send_many_users( $msg , $userlist , $intro = false ){} // посылка сообщения нескольким пользователям public function delete_message( $messageid ){} // удаление сообщения } |
поле user_id_field нужно для того, чтобы пристроить скрипт к Вашей бд, в которой возможно уже есть система пользователей.
Полная реализация всех методов
Как использовать
Создаем экземпляр класса dialog
<?php $dialog = new dialog( $db ,time(), $userid ,isset( $_REQUEST [ 'hash' ])? $_REQUEST [ 'hash' ]: '' ); |
где $db инициализированный и подключенный экземпляр класса db, а $userid это id текущего пользователя.
Получить все диалоги пользователя
$dialogs = $xddialog ->get_user_dialogs(); $out = '' ; foreach ( $dialogs as $dg ) $out .=' <div class = "dialog '.(!$dg['msg_status']?'newmsg':'').'" > <div class = "float_left" > <span class = "nikname" ><a href= "#" id= "user_'.$dg['senderid'].'" > '.$dg[' sender_name '].' </a></span> <span class = "message_time" > '.date(' H:i:s d/m/Y ',$dg[' public ']).' </span> <div> '.$dg[' message '].' </div> </div> <div class = "float_right" > <input class = "btn gotodialog" id= "dialog_'.$dg['hash'].'" value= "ПЕРЕЙТИ К ДИАЛОГУ" /> </div> <div class = "clearex" ></div> </div>'; |
получить все сообщения из текущего дилога
$cnt = $xddialog ->get_new_messages_cnt(); $messages = (! $cnt )? array (): $xddialog ->get_messages_from_dialog(); $out = '' ; foreach ( $messages as $msg ) $out .=' <div> <div class = "float_left" > <span class = "nikname" ><a href= "#" id= "user_'.$msg['senderid'].'" > '.$msg[' sender_name '].' </a></span> <span class = "message_time" > '.date(' H:i:s d/m/Y ',$msg[' public ']).' </span> <div> '.$msg[' message '].' </div> </div> <div class = "clearex" ></div> </div>'; |
получить только новые сообщения
1 2 | $cnt = $xddialog ->get_new_messages_cnt(); $messages = (! $cnt )? array (): $xddialog ->get_messages_from_dialog(true); |
Отправка сообщения в диалог
$xddialog ->send( $_POST [ 'message' ]); |
Поиск подходящего диалога и добавление туда всех пользователей
$xddialog ->find_suit_dialog( array ( $userid1 , $userid2 , $userid3 ,)); $xddialog ->add_users_to_dialog( array ( $userid , $userid1 , $userid2 , $userid3 ,)); |
где $userid это id текущего пользователя, а $userid,$userid1,$userid2,$userid3, id пользователей с которыми будет вестись диалог
Протестировать систему можно скачав пример с гитхаба, либо на моем сайте в demo.
Разумеется, это мой очередной велосипед.
Комментарии
чтобы получить все новые сообщения
Например:
a1: bla bla
a2: bla bla
a3: bla bla
И так далее!
А вообще это же всего лишь пример)
$db->update('user',array('name'=>'user'.$id,'id ='.$id); // заменяем имя на user{номер}
Спасибо заранее!
В итоге при отправке сообщений одному и тому же пользователю в БД каждый раз создаётся новый диалог?!
А всё из-за логики, а вернее всякое отсутствие логики.
Вот, например, запрос на поиск подходящего диалога (функция public function find_suit_dialog($userlist = array()) ...) выглядит следующим образом:
select count(utd.userid) as cnt,utd.dialogid,dg.hash from #_user_to_dialog as utd left join #_dialog as dg on dg.id=utd.dialogid
where utd.dialogid in (select dialogid from #_user_to_dialog where userid='.intval($userlist[0]).' and dialogid in(select dialogid from #_user_to_dialog where userid='.$this->userid.')) group by utd.dialogid;
Что переводится дословно по-русски: выбрать значения из таблицы то кому адресовано сообщение ГДЕ идентификатор диалога равен выборке идентификатора диалога из этой же таблицы. Это что за идиотизм? Выбрать самого себя что-ли. Или выбрать той записи, которой не существует?!
find_suit_dialog - метод который ищет подходящий диалог для юзера, если вы раньше писали чуваку, то берется он, но если в вашем диалоге учавствовал третий юзер, то этот диалог не подходит и создается новый.
говоря по русски: берем количество юзеров в диалоге, номер диалога и его хеш для каждого диалога где был задействован сам юзер и при этом был задействован его собеседник.
Все вроде верно, предложите свое написание подобного запроса, я внесу правки в исходники. Проект таки опенсорс и работает уже в куче проектов. Разумеется доработанный, когда писал эту статью он был сырой.
Там логика такая же, но просто используется не Ajax для послания запросов к серверу, а используется серверный JS, например, Node.js. Если посылать так запросы каждую секунду серверу на то, нет ли там чего-нибудь новенького, то сервер упадёт при большом количестве одновременно подключенных пользователей.
Это я про ваш пример на гитхабе!
Например, первое: строка 154 у Вас
$this->db->getRows('select userid from #_user_to_dialog where dialogid='.$this->id); // под вопросом, нужно ли добавлять отправителю сообщение
ответ: нет, не добавлять! Зачем добавлять информацию для отправителя? Чтобы при подсчёте новых сообщений для него же самого его же сообщение отображалось и считалось, как новое!
2 пример: строка 67 у Вас
if( !$this->id and (!$this->hash or !($this->id = $this->db->exists('dialog',$this->db->escape($this ->hash),'hash','','id'))) )
Для чего результат работы функции сохранять в атрибут класса?
3 пример: самое главное!!!
при поиске диалога 69 строка
$dlgs = $this->db->getRows('select count(utd.userid) as cnt,utd.dialogid,dg.hash from #_user_to_dialog as utd left join #_dialog as dg on dg.id=utd.dialogid where utd.dialogid in (select dialogid from #_user_to_dialog where userid='.intval($userlist[0]).' and dialogid in(select dialogid from #_user_to_dialog where userid='.$this->userid.')) group by utd.dialogid;');
диалог найден если id первого пользователя из массива userlist существует в таблице и при этом в этой же таблице существует id пользователя который отправляет сообщение.
Но!!! Что если, пользователь с id 1 первый раз отправил сообщение пользователям с id 2,3,4, создался диалог с id, скажем 8.В следующий раз тот же пользователь с id номер 1 отправил новое сообщение, только уже пользователям с id 2,5,7,10. Должен создаться новый диалог, так как списки совершенно разные! Но не тут то было!!! Найдя в первой ячейке userlist id пользователя под номером, 2 скрипт посчитает, что диалог есть, и пользователей 2,5,7,10 добавит в диалог 8.
Это неправильно, неккоректно и нарушает конфиденциальность переписки. Посути пользователи с id 5,7,10 смогут читать переписку пользователей с id 2,3,4 в диалоге с id 8.
Кому нужна помощь обращайтесь: http://vk.com/id60635284
Можете сходу назвать технологию которая бы позволила завести этот чат на подавляющем количестве серверов?
А про код, так он писался сугубо в академических целях и применялся всего один раз на практике в развивающемся проекте, в котором был с успехом заменен когда пришел народ и соответственно деньги.
Я его не дорабатываю. Все лежит на гитхабе, почему бы вам не прислать мне пару пул реквестов к скрипту и сделать его лучше. А не голословно кричать что скрипт плох
Самый дешевый, и реально работающий во всех браузерах это конечно longpool
Только вот никак не могу его приделать к блогу на wordpress. Может кто подскажет что и где надо переписать, что бы был возможен обмен сообщениями между зарегистрированными пользователями? Только за ранее предупреждаю в php я ни черта не понимаю так что если возможно, то конкретней)
С наступившим!
строка 31 private $_logid = 0; // если указать 1 то будет лошировать всеоперации