# 基于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>
~~~
- 序言及更新日志
- 前言一 开发PHP必备的环境(你可以不看)
- LinUX系统ThinkPHP5链接MsSQL数据库的pdo_dblib扩展
- centos7.2挂载硬盘攻略
- Centos系统Redis安装及Redis的PHP扩展安装
- Centos系统增加Swap(系统交换区)的方法
- 前言二 开发PHP软件配置和介绍(你依然可以不看)
- 数据库SQL文件
- 本地Git(版本控制)的搭建
- GIT远程仓库的克隆和推送
- Git常用命令
- PHP面向对象思想实战经验领悟
- PHP面向对象实战----命名空间
- PHP面向对象实战----继承
- 基类实战--底层方法封装
- 基类实战--构造函数实战
- 基类实战--析构函数的使用
- TP5实战开发前篇---控制器(controller)
- 控制器中Request类的使用
- 控制器中基类的使用
- TP5实战开发前篇---模型篇(model)
- TP5实战开发前篇---验证器篇(Validate)
- TP5实战课程入门篇---花拳绣腿
- 模块以及类的文件的建立
- Api开发------单条信息显示
- Api开发---单条信息复杂关联显示
- Api开发---查询信息缓存Cache的应用
- TP5实战技巧---开发思路 引路造桥
- TP5实战技巧---整合基类 化繁为简
- TP5实战课程入门篇---数据操作
- Api开发---数据的添加和修改
- API开发---快速开发API通用接口
- TP5专用微信sdk使用教程
- THINKPHP5微信SDK更新记录及升级指导
- TP5专用SDK 微信参数配置方法
- 微信公众号推送接口对接教程
- 微信推送接口对接示例含扫描登录微信端部分
- TP5专用微信支付SDK使用简介
- TP5专用支付宝支付SDK使用说明
- 使用NW将开发的网站打包成桌面应用
- TP5高阶实战课程 进阶篇概述
- 进阶篇一 实战开发之习惯及要求
- 进阶篇二 实战开发之控制器
- 控制器基类之控制器基类使用方法
- 控制器基类之控制器基类常用方法分享
- 控制器基类之构造函数的使用方法
- 进阶篇三 实战开发之权限控制
- TP5实战源码 --- 全局用户信息验证类Auth
- TP5实战源码 --- 微信Auth实战开发源码
- 进阶篇四 实战开发之模型
- 模型基类之模型基类的用途
- 模型基类之常用数据处理方法
- 模型逻辑层之实战代码(含事务)
- 模型实战开发之模型常用方法
- 模型实战源码 --- 乐观锁的应用
- 模型实战技巧---Model事件功能的使用
- 模型事件实战应用---数据库操作日志
- 进阶篇五 实战开发之缓存(Cache)
- TP5实战源码---应用缓存获取城市信息
- TP5实战源码---应用缓存获取分类详情
- 进阶篇六 TP5类库的封装和使用
- DataEdit快捷操作类库
- ShowCode快捷使用类库
- 阿里大于 短信API接口 TP5专用类库
- DatabaseUpgrade数据库对比及更新类库
- AuthWeb权限类使用说明
- 进阶篇七 服务层的应用
- 服务层源码示例
- 服务层基类源码
- 进阶篇八 应用层Redis数据处理基类
- Redis服务层基类源码
- 进阶篇九 使用Redis类库处理一般的抢购(秒杀)活动示例
- 进阶篇十 某大型项目应用本Redis类源码示例(含事务 乐观锁)
- 进阶篇十一 逻辑层的应用
- 逻辑层基类源码
- 进阶篇 服务层代码示例
- 高阶实战课程 进阶篇持续新增中
- 高阶篇一 TP5命令行之守护任务源码
- TP5实战源码 --- 命令行
- TP5实战源码 --- 通过shell建立PHP守护程序
- 高阶篇二 使用Redis队列发送微信模版消息
- 高阶篇二 之 Worker队列基类源码
- 高阶篇三 TP5实战之Redis缓存应用
- Redis实战源码之Hash专用类库源码
- Redis实战源码之Model类结合
- Redis实战源码之模型Hash基类源码
- Redis实战源码之Hash查询使用技巧
- Redis实战源码之 shell脚本中redis赋值和取值
- 高阶篇四 Swoole的实战应用
- swoole基类代码
- Swoole扩展WebsocketServer专用类
- 基于Swoole的多Room聊天室的程序
- Swoole守护服务shell源码
- 高阶篇五 命令行异步多进程队列类的应用
- tp_worker类源码
- WorkerBase
- WorkerCommand
- WorkerRedis
- Redis类
- CycleWorkBase
- WorkerHookBase异步钩子
- 队列日志SQL
- 高阶篇六 定时执行队列类库以及使用方法
- 定时队列类库源码
- 高阶篇七 异步执行循环队列类库以及使用教程
- CycleWorkBase源码
- 高阶实战课程 进阶篇持续新增中
- Extend便捷类库源码库
- 阿里相关类库
- SendSms--验证码API接口文件
- 权限相关类库目录
- AuthWeb 权限验证类库
- Redis便捷操作类库(20171224更新)
- Redis
- Tools工具类库集
- Curl类库
- DataEdit
- Rand类库
- ShowCode类库
- Upload类库
- 附件集合
- 附件一:微信支付 实战开发源码
- 微信支付类库源代码
- Common_util_pub.php
- DownloadBill_pub.php
- JsApi_pub.php
- NativeCall_pub.php
- NativeLink_pub.php
- OrderQuery_pub.php
- Refund_pub.php
- RefundQuery_pub.php
- SDKRuntimeException.php
- ShortUrl_pub.php
- UnifiedOrder_pub.php
- Wxpay_client_pub.php
- Wxpay_server_pub.php
- WxPayConf_pub.php
- 微信支付回调页面源码
- 附件二 顺丰快递BSP接口实战开发源码
- 顺丰快递BSP接口实战开发源码
- 顺丰BSP基类
- 顺丰BSP基础代码
- 顺丰BSP下单接口
- 顺丰BSP查单接口
- 顺丰BSP确认/取消接口
- 附件三 APP注册登陆接口源码(含融云平台接口)
- 附件四 TP5订单Model(含事务 获取器 修改器等方法)
- 附录五 RSA加密解密
- Rsa文件源码
- 附件六 阿里大于短信接口
- 附件七 AES加解密类
- AES加解密类源码
- 附件八 TP5路由设置源码
- 附件九 TP5 Excel导入导出下载便捷类库
- Excel类库TP5源码
- 附件十 TP5便捷操作Redis类库源码
- TP5源码 Redis操作便捷类库
- 附件十一 TP5源码 上传文件入库类源码
- 上传类Upload源码
- Upload类上传配置文件
- 存储图像文件的数据库SQL文件
- 存储文件的数据库SQL文件
- 附件十二 TP5 图片处理增强类 支持缩略图在线显示
- 附件十三 微信推送消息接口类库源码
- 附件十三 微信推送消息接口类库源码 之 基类
- 附件十四 存储微信昵称的处理方法