# 基于Swoole的多Room聊天室的程序 [TOC] ## 聊天室Command源码 ~~~ <?php /** * Created by PhpStorm. * Power By Mikkle * Email:776329498@qq.com * Date: 2017/11/6 * Time: 11:51 */ namespace app\base\command; use app\base\command\webim\WebSocket; use think\console\Command; use think\console\Input; use think\console\Output; class Server extends Command { protected $swoole ; public function configure(){ $this->setName('socketServer')->setDescription('Here is socket service '); } public function execute(Input $input, Output $output) { $this->swoole = new WebSocket(); } } ~~~ ## Command源码文件 详细教程参见 https://www.kancloud.cn/mikkle/thinkphp5_study/376459 ~~~ <?php return [ 'app\base\command\Mikkle', 'app\base\command\Server', ]; ~~~ ## 聊天室后台源码 ~~~ <?php /** * Created by PhpStorm. * Power By Mikkle * Email:776329498@qq.com * Date: 2017/11/7 * Time: 11:04 */ namespace app\base\command\webim; use app\base\controller\Redis; use app\base\controller\Rsa; use mikkle\tp_swoole\WebsocketServer; use think\Cache; use think\Config; use think\Exception; use think\Log; use think\Db; class WebSocket extends WebsocketServer { protected $redis; protected $userList = "UserOnlineList"; protected $roomName = "defaultRoom"; protected $roomListName = "RoomList"; protected $error; protected $cache = true; /** * 初始化的回调方法 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server */ protected function initialize(\swoole_websocket_server $server) { $redis_config = Config::get("command.redis"); $this->redis = new Redis($redis_config); $roomList = $this->redis->smembers($this->roomListName); //主程序启动 清空所有聊天室在线用户 if (!empty($roomList) && is_array($roomList)) { foreach ($roomList as $room) { $this->redis->delete("{$this->userList}_{$room}"); } } //创建内存表 $this->createTable(); } /** * 创建链接时候的回调方法 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server * @param \swoole_http_request $request */ protected function onOpen(\swoole_websocket_server $server, \swoole_http_request $request) { echo "server: success with fd{$request->fd}\n"; } /** * 接收信息回调方法 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server * @param \swoole_websocket_frame $frame */ protected function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame) { try { if (!empty($frame) && $frame->opcode == 1 && $frame->finish == 1) { $message = $this->checkMessage($frame->data); if (!$message) { $this->serverPush($server, $frame->fd, $frame->data, 'message'); } if (isset($message["type"])) { switch ($message["type"]) { case "login": $this->login($server, $frame->fd, $message["message"], $message["room"]); break; case "message": $this->serverPush($server, $frame->fd, $message["message"], 'message', $message["room"]); break; default: } $this->redis->sadd($this->roomListName, $message["room"]); } } else { throw new Exception("接收数据不完整"); } } catch (Exception $e) { Log::error($e->getMessage()); } echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n"; } protected function onRequest(\swoole_websocket_server $server, \swoole_http_request $request, \swoole_http_response $response) { // 接收http请求从get获取message参数的值,给用户推送 // 通过这个接口 可以对做自定义的管理推送 } /** * 退出的回调方法 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server * @param $fd */ protected function onClose(\swoole_websocket_server $server, $fd) { $this->logout($server, $fd); $server->close($fd); echo "client {$fd} closed\n"; } /** * 推送同聊天室信息 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server * @param $frame_fd * @param string $message * @param string $message_type */ protected function serverPush(\swoole_websocket_server $server, $frame_fd, $message = "", $message_type = "message") { $push_list = $this->getPushListByFd($frame_fd); $message = htmlspecialchars($message); $datetime = date('Y-m-d H:i:s', time()); $user = $this->table->get($frame_fd); if (isset($user)) { unset($user["openid"]); foreach ($push_list as $fd) { if ($fd == $frame_fd) { continue; } @$server->push($fd, json_encode([ 'type' => $message_type, 'message' => $message, 'datetime' => $datetime, 'user' => $user, ]) ); } } } /** * 获取同聊天室用户信息 * Power: Mikkle * Email:776329498@qq.com * @param $fd * @return array */ protected function getPushListByFd($fd) { $room = $this->table->get($fd, "room"); if (empty($room)) { return []; } return $this->redis->hget("{$this->userList}_{$room}"); } /** * 检测信息并转换信息 * Power: Mikkle * Email:776329498@qq.com * @param $message * @return array|bool|mixed */ protected function checkMessage($message) { $message = json_decode($message); $return_message = []; if (!is_array($message) && !is_object($message)) { $this->error = "接收的message数据格式不正确"; return false; } if (is_object($message)) { foreach ($message as $item => $value) { $return_message[$item] = $value; } } else { $return_message = $message; } if (!isset($return_message["type"]) || !isset($return_message["message"])) { return false; } else { if (!isset($return_message["room"])) $return_message["room"] = $this->roomName; return $return_message; } } /** * 处理用户登录信息 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server * @param $frame_fd * @param string $message * @param $room * @return bool */ protected function login(\swoole_websocket_server $server, $frame_fd, $message = "", $room) { $open_id = Rsa::instance()->decrypt($message); if (empty($open_id)) { return false; } $user_info = $this->getUserInfoByOpenId($open_id); $this->updateFrameFd($frame_fd, $user_info); $user_info["fd"] = $frame_fd; $user_info["room"] = $room; $this->updateFrameFd($frame_fd, $user_info); $this->createRoomUserList($server, $room, $frame_fd, $open_id); unset($user_info["openid"]); $server->push($frame_fd, json_encode( ['user' => $user_info, 'all' => $this->allUserByRoom($room),'type' => 'openSuccess']) ); $this->serverPush($server, $frame_fd, "欢迎{$user_info['name']}进入聊天室", 'open'); } /** * 用户退出处理 * Power: Mikkle * Email:776329498@qq.com * @param \swoole_websocket_server $server * @param $fd */ protected function logout(\swoole_websocket_server $server, $fd) { $user = $this->table->get($fd); if (isset($user)) { $this->serverPush($server, $fd, $user['name'] . "离开聊天室", 'close'); $this->deleteRoomUserList($user["room"], $user["openid"]); $this->table->del($fd); } } /** * 添加至聊天室列表 * Power: Mikkle * Email:776329498@qq.com * @param $server * @param $room * @param $frame_fd * @param $openid */ protected function createRoomUserList(\swoole_websocket_server $server, $room, $frame_fd, $openid) { try { $fd = $this->redis->hget("{$this->userList}_{$room}", $openid); if (isset($fd)) { $user = $this->table->get($fd); if (isset($user)) { $this->serverPush($server, $fd, $user['name'] . "离开聊天室", 'close'); $this->table->del($fd); } $server->close($fd); } $this->redis->hset("{$this->userList}_{$room}", $openid, $frame_fd); } catch (Exception $e) { Log::error($e->getMessage()); } } /** * 从在线用户列表中删除用户 * Power: Mikkle * Email:776329498@qq.com * @param $room * @param $openid */ protected function deleteRoomUserList($room, $openid) { $this->redis->hdel("{$this->userList}_{$room}", $openid); } /** * 获取所有聊天室在线信息 * Power: Mikkle * Email:776329498@qq.com * @param $room * @return array */ protected function allUserByRoom($room) { $user_list = $this->redis->hget("{$this->userList}_{$room}"); $users = []; if (!empty($user_list)) { foreach ($user_list as $fd) { $user = $this->table->get($fd); if (!empty($user)) { $users[] = $user; } } } return $users; } /** * 存在在线链接信息 * Power: Mikkle * Email:776329498@qq.com * @param $frame_fd * @param $user_info */ protected function updateFrameFd($frame_fd, $user_info) { $this->table->set($frame_fd, $user_info); } /** * 获取微信用户 * Power: Mikkle * Email:776329498@qq.com * @param $open_id * @return array|false|mixed|\PDOStatement|string|\think\Model */ protected function getUserInfoByOpenId($open_id) { $user_info = Cache::get("WeFans:{$open_id}"); if (empty($user_info) || $this->cache == false) { $user_info = Db::table("mk_we_fans")->where(["openid" => $open_id, "status" => 1])->field("openid,nickname_json,nickname,headimgurl as avatar")->find(); if (!$user_info) return []; if (isset($user_info["nickname_json"]) && !empty(json_decode($user_info["nickname_json"]))) { $user_info["name"] = json_decode($user_info["nickname_json"]); } else { $user_info["name"] = $user_info["nickname"]; } unset($user_info["nickname_json"]); unset($user_info["nickname"]); Cache::set("WeFans:{$open_id}", $user_info); } return $user_info; } /** * 创建内存表 存在在线用户 * Power: Mikkle * Email:776329498@qq.com */ protected function createTable() { $this->table = new \swoole_table(1024); $this->table->column('fd', \swoole_table::TYPE_INT); $this->table->column('openid', \swoole_table::TYPE_STRING, 100); $this->table->column('name', \swoole_table::TYPE_STRING, 255); $this->table->column('avatar', \swoole_table::TYPE_STRING, 255); $this->table->column('room', \swoole_table::TYPE_STRING, 100); $this->table->create(); } } ~~~ ## 聊天室前端源码 ~~~ <?php /** * Created by PhpStorm. * Power By Mikkle * Email:776329498@qq.com * Date: 2017/9/20 * Time: 15:26 */ namespace app\center\controller; use app\base\controller\Rsa; use think\Exception; class ChatRoom extends WeAuth { public function index() { $assign_data = [ //你自己服务器的地址 "server"=>"ws://wechat.com:9501", "open_id"=>Rsa::instance()->encrypt($this->open_id), ]; $this->assign($assign_data); return $this->fetch('chatRoom/index'); } } ~~~ ## 聊天室模版 ~~~ <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>在线聊天室</title> {include file="base_mui/chat_room"/} <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <![endif]--> </head> <body id="webim" > <div class="col-xs-12 " style="padding-left: 0px;padding-right: 0px"> <div class="panel panel-info"> <!--<div class="panel-heading" style="text-align: center">--> <!--YZF聊天室--> <!--</div>--> <div class="panel-body no-padding"> <div class="col-xs-2 user-list"> </div> <div class="col-xs-10 no-padding"> <div class="chat-list"> </div> </div> </div> <div class="message" > <div class="text" style="border: 1px solid black"> <textarea></textarea> </div> <div class="send"> 发送 </div> </div> </div> </div> <script> var uuid = "{$open_id}"; $(function() { webim.init(); $(".message .send").click(function(){ webim.sendMsg(); }); }); var config = { server : '{$server}' }; var webim = { data : { wsServer : null, info : {} }, init : function() { this.data.wsServer = new WebSocket(config.server); this.open(); this.close(); this.messages(); this.error(); }, open : function() { this.data.wsServer.onopen = function(evt) { webim.notice('连接成功'); webim.data.wsServer.send(webim.toJson("login",uuid)); } }, close : function() { this.data.wsServer.onclose = function(evt) { webim.notice('不妙,链接断开了'); } }, messages : function() { this.data.wsServer.onmessage = function (evt) { var data = jQuery.parseJSON(evt.data); switch (data.type) { case 'open': webim.appendUser(data.user.name, data.user.avatar, data.user.fd); webim.notice(data.message); console.log(data); break; case 'close': webim.removeUser(data.user.fd); webim.notice(data.message); console.log(data); break; case 'openSuccess': webim.data.info = data.user; webim.showAllUser(data.all); console.log(data); break; case 'message': webim.newMessage(data); console.log(data); break; } }; }, error : function() { this.data.wsServer.onerror = function (evt, e) { console.log('Error occured: ' + evt.data); }; }, removeUser: function(fd) { $(".fd-"+fd).remove(); }, showAllUser: function(users) { for (i in users) { this.appendUser(users[i].name, users[i].avatar, users[i].fd); } }, toJson:function (type,msg) { var data={ "type":type, "message":msg, } console.log(JSON.stringify(data)); return JSON.stringify(data); }, sendMsg : function() { var text = $(".message .text textarea"); var msg = text.val(); if ($.trim(msg) == '') { this.layerErrorMsg('请输入消息内容'); return false; } this.data.wsServer.send(this.toJson("message",msg)); var html = '<div class="col-xs-10 msg-item ">' +'<div class="col-xs-1 no-padding pull-right">' +'<div class="avatar">' +'<img src="'+this.data.info.avatar+'" width="50" height="50" class="img-circle">' +'</div>' +'</div>' +'<div class="col-xs-11" style="padding-right: 0px;padding-left: 0">' +'<div class="col-xs-12" style="padding-right: 0px;padding-left: 0">' +'<div class="username pull-right">'+this.data.info.name+'</div>' +'<div>' +'<div class="col-xs-12 no-padding" style="padding-left: 50px">' +'<div class="msg pull-right">'+msg+'</div>' +'</div>' +'</div>'; $('.chat-list').append(html); this.appendUser(this.data.info.name, this.data.info.avatr, this.data.info.fd); this.scrollBottom(); text.val(''); }, newMessage : function(data) { this.appendUser(data.user.name, data.user.avatar, data.user.fd); var html = '<div class="col-xs-10 msg-item ">' +'<div class="col-xs-1 no-padding">' +'<div class="avatar">' +'<img src="'+data.user.avatar+'" width="50" height="50" class="img-circle">' +'</div>' +'</div>' +'<div class="col-xs-11 no-padding">' +'<div class="col-xs-12">' +'<div class="username">'+data.user.name+'</div>' +'</div>' +'<div class="col-xs-12 no-padding">' +'<div class="msg">'+data.message+'</div>' +'</div>' +'</div>' +'</div>'; $('.chat-list').append(html); this.scrollBottom(); }, scrollBottom : function() { $('.chat-list').scrollTop($('.chat-list')[0].scrollHeight ); }, notice : function(msg) { var html ='<div class="col-xs-12 notice text-center">'+msg+'</div>'; $('.chat-list').append(html); this.scrollBottom(); }, appendUser : function(name, avatar, fd) { if ($(".fd-"+fd).length > 0) { return true; } var html = ' <div class="user-item fd-'+fd+'">' +'<div class="avatar">' +'<img src="'+avatar+'" width="50" height="50" class="img-circle">' +'</div>' +'<div class="user-name">'+name+'</div>' +'</div>'; $(".user-list").append(html); $('.user-list').scrollTop($('.user-list')[0].scrollHeight ); }, layerSuccessMsg : function(msg) { layer.msg(msg, {time: 1000, icon:6}); }, layerErrorMsg : function(msg) { layer.msg(msg, {time: 1000, icon:5}); } }; var html = document.querySelector('html'); var rem = html.offsetWidth / 7.2; html.style.fontSize = rem + "px"; </script> </body> </html> ~~~