最近公司的项目需要做一个简单的聊天,项目用的框架是thinkphp 5,开发任务主要是实现买卖双方生成订单后,可以在线交流。
无意间在tp手册上看到 workerman socket包,然后就安装了试试。
实现的思路是,将订单的id作为房间号房间人员指定为订单双方的用户,数据库中将用户id与的客户端fd对应,用户进入时,登录时将用户id与客户端fd存入数据库,发送时根据用户fd发送给当前用户,断开连接时,将用户fd清除。
需要的资料:
workerman手册 http://doc.workerman.net/
下边先介绍下包的下载
1.
首先通过 composer 安装
composer require topthink/think-worker
如果需要在window下做服务端,还需要
composer require workerman/workerman-for-win
数据库操作包
composer require workerman/mysql
或者可以将tp5的DB类引进来,或者使用redis,本文只是将流程写下来,项目使用还是需要将代码优化
下载完成后会多出来这两个文件夹,然后我们将workerman 启动命令添加到tp5中
在下边的目录下创建Workerman.php文件
setName('workerman')
->addArgument('action', Argument::OPTIONAL, "action start|stop|restart")
->addArgument('type', Argument::OPTIONAL, "d -d")
->setDescription('workerman chat');
}
protected function execute(Input $input, Output $output)
{
global $argv;
$action = trim($input->getArgument('action'));
$type = trim($input->getArgument('type')) ? '-d' : '';
$argv[0] = 'chat';
$argv[1] = $action;
$argv[2] = $type ? '-d' : '';
$this->start();
}
private function start()
{
$this->startGateWay();
$this->startBusinessWorker();
$this->startRegister();
Worker::runAll();
}
private function startBusinessWorker()
{
$worker = new BusinessWorker();
$worker->name = 'BusinessWorker';
$worker->count = 1;
$worker->registerAddress = '127.0.0.1:1236';
$worker->eventHandler = Events::class;
}
private function startGateWay()
{
$gateway = new Gateway("websocket://0.0.0.0:8282");
$gateway->name = 'Gateway';
$gateway->count = 1;
$gateway->lanIp = '127.0.0.1';
$gateway->startPort = 2300;
$gateway->pingInterval = 30;
$gateway->pingNotResponseLimit = 0;
$gateway->pingData = '{"type":"@heart@"}';
$gateway->registerAddress = '127.0.0.1:1236';
}
private function startRegister()
{
new Register('text://0.0.0.0:1236');
}
}
然后将'app\common\command\Workerman', 加入到 command.php 文件里边
然后再创建个监听文件Events.php
select('*')->where('room_id= :id')->bindValues(['id'=>$message_data['room_id']])->from('de_chat_log')->orderByDESC(['id'])->limit(10)->offset($offset)->query();
//print_r($log);
$log=array_reverse($log);
$new_message = array(
'type'=>$message_data['type'],
'client_id'=>$client_id,
'room_id'=>$message_data['room_id'],
'content'=>json_encode($log),
'time'=>date('Y-m-d H:i:s'));
Gateway::sendToCurrentClient(json_encode($new_message));
break;
// 客户端登录 message格式: {type:login, phone:xx, order_id:1} ,添加到客户端,广播给所有客户端xx进入聊天室
case 'login':
if(isset($message_data['order_id'])) {
#房间不存在 创建房间
global $db;
$order =$db->select('*')->where('id= :id')->bindValues(array('id'=>$message_data['order_id']))->from('de_kt_detail')->row();
#判断是否建过房间
$is_room=$db->select('*')->where('user_id= :user_id AND party_id= :party_id')->bindValues(array('user_id'=>$order['user_id'],'party_id'=>$order['buy_id']))->from('de_room')->row();
if (empty($is_room)){
$is_room=$db->select('*')->where('user_id= :user_id AND party_id= :party_id')->bindValues(array('user_id'=>$order['buy_id'],'party_id'=>$order['user_id']))->from('de_room')->row();
if (empty($is_room)){
#创建房间
$user_info=$db->select('mobile,id')->where('id= :id')->bindValues(array('id'=>$order['user_id']))->from('de_users')->row();
$party_info=$db->select('mobile,id')->where('id= :id')->bindValues(array('id'=>$order['buy_id']))->from('de_users')->row();
// if ($order['user_id']==$order['buy_id']){
// #创建房间异常
// $new_message=[
// 'status'=>203,
// 'message'=>'房间创建失败'
// ];
// return Gateway::sendToCurrentClient(json_encode($new_message));
// }
$room_id_db = $db->insert('de_room')->cols(array('user_id'=>$order['user_id'],'user_phone'=>$user_info['mobile'],'accept_phone'=>$party_info['mobile'],'party_id'=>$order['buy_id']))->query();
if ($room_id_db==''){
#创建房间异常
$new_message=[
'status'=>201,
'message'=>'房间创建失败'
];
return Gateway::sendToCurrentClient(json_encode($new_message));
}
$is_room['id']=$room_id_db;
}
}
#查询当前用户id
$user=$db->select('mobile,id')->where('mobile= :phone')->bindValues(array('phone'=>$message_data['phone']))->from('de_users')->row();
$fd=$db->select('*')->where('id= :id')->bindValues(array('id'=>$is_room['id']))->from('de_room')->row();
// 判断是否有房间是否开启
//print_r($client_id);
if ($user['id']==$fd['user_id']){
$db->update('de_room')->cols(array('user_fd'=>$client_id))->where('id= :id')->bindValues(array('id'=>$is_room['id']))->query();
}
if ($user['id']==$fd['party_id']){
$db->update('de_room')->cols(array('party_fd'=>$client_id))->where('id= :id')->bindValues(array('id'=>$is_room['id']))->query();
}
}else{
$new_message=[
'status'=>202,
'message'=>'参数异常'
];
return Gateway::sendToCurrentClient(json_encode($new_message));
}
$log =$db->select('*')->where('room_id= :id')->bindValues(['id'=>$is_room['id']])->from('de_chat_log')->orderByDESC(['id'])->limit(10)->offset(0)->query();
$log=array_reverse($log);
// print_r($log);
$new_message = array(
'type'=>$message_data['type'],
'client_id'=>$client_id,
'room_id'=>$is_room['id'],
'content'=>json_encode($log),
'time'=>date('Y-m-d H:i:s'));
Gateway::sendToCurrentClient(json_encode($new_message));
return;
// 客户端发言 message: {type:say, to_client_id:xx, room_id:xx, content:xx}
case 'say':
// 通过全局变量获得db实例
global $db;
#判断是否是图文
if (isset($message_data['is_image'])){
if ($message_data['is_image']==2){
$log['is_img']='2';
}else{
$log['is_img']='1';
}
}
#房间所有用户
$fd=$db->select('*')->where('id= :id')->bindValues(array('id'=>$message_data['room_id']))->from('de_room')->row();
if ($fd['user_fd']!=''&&$fd['party_fd']!=''){
#双方在线
#存储记录
if ($fd['user_fd']==$client_id){
$log['user_id']=$fd['user_id'];
$log['user_phone']=$fd['user_phone'];
$log['accept_id']=$fd['party_id'];
$log['accept_phone']=$fd['accept_phone'];
$to_client_id=$fd['party_fd'];
}else{
$log['user_id']=$fd['party_id'];
$log['user_phone']=$fd['accept_phone'];
$log['accept_id']=$fd['user_id'];
$log['accept_phone']=$fd['user_phone'];
$to_client_id=$fd['user_fd'];
}
$log['message']=$message_data['content'];
$log['room_id']=$message_data['room_id'];
$log['create_time']=date('Y-m-d H:i:s');
$log['is_read']='2';
// $new_message = [
// 'type'=>'say',
// 'from_client_id'=>$client_id,
// 'from_client_name' =>'1',
// 'to_client_id'=>$to_client_id,
// 'content'=>nl2br(htmlspecialchars($log)),
// 'time'=>date('Y-m-d H:i:s'),
// ];
$new_message = array(
'type'=>$message_data['type'],
'client_id'=>$client_id,
'room_id'=>$message_data['room_id'],
'content'=>json_encode($log),
'time'=>date('Y-m-d H:i:s')
);
$db->insert('de_chat_log')->cols($log)->query();
Gateway::sendToClient($to_client_id, json_encode($new_message));
return Gateway::sendToCurrentClient(json_encode($new_message));
}else{
#接收方未在线
if ($fd['user_fd']==$client_id){
$log['user_id']=$fd['user_id'];
$log['user_phone']=$fd['user_phone'];
$log['accept_id']=$fd['party_id'];
$log['accept_phone']=$fd['accept_phone'];
}else{
$log['user_id']=$fd['party_id'];
$log['user_phone']=$fd['accept_phone'];
$log['accept_id']=$fd['user_id'];
$log['accept_phone']=$fd['user_phone'];
}
#查询接受方用户id
// print_r($accept_id);
$log['message']=$message_data['content'];
$log['room_id']=$message_data['room_id'];
$log['create_time']=date('Y-m-d H:i:s');
$log['is_read']='1';
print_r($log);
$db->insert('de_chat_log')->cols($log)->query();
}
$new_message = [
'type'=>'say',
'from_client_id'=>$client_id,
'from_client_name' =>$log['user_phone'],
'to_client_id'=>'',
'content'=>nl2br(htmlspecialchars($message_data['content'])),
'time'=>date('Y-m-d H:i:s'),
];
return Gateway::sendToCurrentClient(json_encode($new_message));
}
}
/**
* 当客户端断开连接时
* @param integer $client_id 客户端id
*/
public static function onClose($client_id)
{
// 从房间的客户端列表中删除
global $db;
$fd=$db->select('*')->where('user_fd= :id OR party_fd= :party_fd')->bindValues(array('id'=>$client_id,'party_fd'=>$client_id))->from('de_room')->row();
if($fd['user_fd']==$client_id){
$db->update('de_room')->cols(array('user_fd'=>NULL))->where('id= '.$fd['id'])->query();
}else{
$db->update('de_room')->cols(array('party_fd'=>NULL))->where('id= '.$fd['id'])->query();
}
echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n";
// if(isset($_SESSION['room_id']))
// {
// $room_id = $_SESSION['room_id'];
// $new_message = array('type'=>'logout', 'from_client_id'=>$client_id, 'from_client_name'=>'', 'time'=>date('Y-m-d H:i:s'));
// Gateway::sendToGroup($room_id, json_encode($new_message));
// }
}
}
然后进入服务器启动socket服务
到此为止服务端就完成了。
CREATE TABLE `de_room` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` int(11) NOT NULL COMMENT '房间创建人',
`user_phone` varchar(255) DEFAULT NULL,
`party_id` int(11) NOT NULL COMMENT '房间人员iD',
`accept_phone` varchar(255) DEFAULT NULL,
`user_fd` varchar(255) DEFAULT NULL COMMENT 'user_id fd',
`party_fd` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8 COMMENT='房间';
CREATE TABLE `de_chat_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '发送方id',
`user_phone` varchar(255) NOT NULL COMMENT '发送方手机号',
`accept_id` int(11) NOT NULL COMMENT '接收方ID',
`accept_phone` varchar(255) NOT NULL COMMENT '发送方手机号',
`room_id` int(11) NOT NULL COMMENT '房间id',
`message` text COMMENT '发送内容',
`create_time` datetime DEFAULT NULL COMMENT '发送时间',
`is_read` enum('1','2') NOT NULL DEFAULT '1' COMMENT '状态:1=未读,2=已读',
`is_img` enum('1','2') NOT NULL DEFAULT '1' COMMENT '是否是图片:1=文本,2=图片',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=116 DEFAULT CHARSET=utf8 COMMENT='聊天记录';