swoole 的练习 demo(6)- 数据库设计与实现
一直不能下决心好好学习,仔细研究一下,决定用尽量降低难度曲线的方法,从易到难,一步一步的学习,所以整了个demo项目。
git仓库和使用步骤
确保能看到swoole,在 php -m 命令中 | |
php -m | |
git clone https://github.com/lang123789/swoole_demo.git | |
然后设置了标签,本文对应 v6.0 | |
cd swoole_demo | |
git checkout v6.0 | |
## 安装类库,生成 autoload.php 文件 | |
composer install | |
## 启动 websocket 服务 | |
php src/WebSocket/run.php |
需求
10、需要把用户的聊天消息保存到数据库。
11、需要校验用户id是否正确,是否在表中存在
设计
建表 和插入假数据。
这里有两张表,1用户表,2聊天记录表。 | |
CREATE TABLE users ( | |
id int(11) NOT NULL AUTO_INCREMENT, | |
user_name varchar(255) NOT NULL DEFAULT '' comment '用户名称', | |
email varchar(255) NOT NULL DEFAULT '' comment 'email', | |
created_at timestamp null default current_timestamp, | |
PRIMARY KEY (`id`) | |
) ENGINE=InnoDB ; | |
CREATE TABLE messages ( | |
id int(11) NOT NULL AUTO_INCREMENT, | |
user_id int not null default 0 comment '用户id', | |
content varchar(3000) NOT NULL DEFAULT '' comment '聊天内容', | |
created_at timestamp null default current_timestamp, | |
PRIMARY KEY (`id`), | |
index user_id(user_id) | |
) ENGINE=InnoDB ; | |
insert into users(id,user_name)values (1,'管理员'); | |
insert into users(id,user_name)values (2,'用户2'); | |
insert into users(id,user_name)values (3,'用户3'); |
数据库插件选型
各方比较之后,选择用 Mix Database
有一说一,是真好用。
官方地址:openmix.org/mix-php/docs/3.0/#/zh-...
安装命令:
composer require mix/database
客户端代码改变 index.php
//假定这是数据库的查询结果 | |
$config = require __DIR__ .'/config/mysql.php'; | |
$db = new \Mix\Database\Database('mysql:host='. $config['host'] .';port='. $config['port'] | |
.';charset='. $config['charset'] .';dbname='.$config['db_name'], $config['username'], $config['password']); | |
$datas = $db->table('users')->where('id = ?', $user_id)->first(); | |
if (empty($datas)){ | |
die('非法用户'); | |
} |
服务端代码改变
// 数据库连接池 | |
private function create_mysql_pool() | |
{ | |
$maxOpen = 50; // 最大开启连接数 | |
$maxIdle = 20; // 最大闲置连接数 | |
$maxLifetime = 3600; // 连接的最长生命周期 | |
$waitTimeout = 0.0; // 从池获取连接等待的时间, 0为一直等待 | |
$config = $this->mysql_config['mysql']; // 读取配置文件。 | |
$this->db = new \Mix\Database\Database('mysql:host='. $config['host'] .';port='. $config['port'] | |
.';charset='. $config['charset'] .';dbname='.$config['db_name'], $config['username'], $config['password']); | |
$this->db->startPool($maxOpen, $maxIdle, $maxLifetime, $waitTimeout); | |
\Swoole\Runtime::enableCoroutine(); // 必须放到最后,防止触发协程调度导致异常 | |
} | |
public function message(\swoole_websocket_server $server, \swoole_websocket_frame $frame) | |
{ | |
$data = $frame->data; | |
//echo "有消息:" . $data . "\n"; | |
//这里用户发来的信息已经分类型了。my_id开头,说明是系统自动从客户端发送的信息,用于识别身份。 | |
if (preg_match('#^ping#', $data)) { // 心跳 | |
// echo "心跳来了 " . date("Y-m-d H:i:s") . "\n"; $server->push($frame->fd, 'pong');// 返回一个消息,过会他会再次传来。 | |
} elseif (preg_match('#^my_id#', $data)) { // 用户上线 | |
$user_id = explode("|", $data)[1]; | |
$datas = $this->db->table('users')->where('id = ?', $user_id)->first(); | |
if (!$datas) { | |
//如果用户不存在,则w我直接关闭连接。 | |
echo "非法连接。用户id:" . $user_id; | |
$server->close($frame->fd); | |
return; | |
} | |
$user_name = $datas->user_name; | |
$user = [ | |
'fd' => $frame->fd, | |
'user_name' => $user_name, | |
'user_id' => strval($user_id), | |
]; | |
echo "有个人刚上线,数据:" . json_encode($user, JSON_UNESCAPED_UNICODE); | |
echo "连接池当前状态:".json_encode( $this->db->poolStats() ); | |
echo "\n"; | |
// 放入内存表 | |
$this->table->set($frame->fd, $user); | |
//给本人看 欢迎您。 | |
$server->push($frame->fd, json_encode([ | |
'type' => 'my_id', | |
'message' => '欢迎您,' . $user_name, | |
])); | |
// 通知其他人 某某 上线了。 | |
foreach ($this->table as $row) { | |
if ($row['fd'] != $frame->fd) { | |
$server->push($row['fd'], json_encode([ | |
'type' => 'my_id', | |
'message' => $user_name . ' 上线了', | |
])); | |
} | |
} | |
} elseif (preg_match('#^my_message#', $data)) { // 用户发消息 | |
$user_id = explode("|", $data)[1]; | |
$message = explode("|", $data)[2]; | |
$datas = $this->db->table('users')->where('id = ?', $user_id)->first(); | |
if (!$datas) { | |
//如果用户不存在,则w我直接关闭连接。 | |
echo "非法连接。用户id:" . $user_id; | |
$server->close($frame->fd); | |
return; | |
} | |
$user_name = $datas->user_name; | |
$data = [ | |
'user_id' => $user_id, | |
'content' => $message, | |
]; | |
$this->db->insert('messages', $data); // 插入数据库 | |
echo "有人发消息,内容:" . $message; | |
echo "连接池当前状态:".json_encode( $this->db->poolStats() ); | |
echo "\n"; | |
// 通知其他人 ,进行广播 | |
foreach ($this->table as $row) { | |
if ($row['fd'] != $frame->fd) { | |
echo "推送消息:" . $message . "给fd:" . $row['fd']; | |
$server->push($row['fd'], json_encode([ | |
'type' => 'my_message', | |
'message' => $message, | |
'from_user_name' => $user_name, | |
])); | |
} | |
} | |
} | |
} |
代码说明
1、这次的代码变动其实比较少,去除了最开始的假的用户表,使用了真实的用户表。
2、也使用了真实的消息表保存用户的消息,上面的代码中能找到 “插入数据库” 的注释。
3、mix 这个类库真好用,代码特别少特别精简。但说明一下,我没有在真实项目中用过 mix。只是这个 demo 中用的。
4、真实的项目中,其实客服是固定的,应该不需要广播,数据库用户表中,添加是否客服的字段,都不难设计的,代码也不难写。
5、也可以有这样的需求,代码对方是否已读,也可以加字段实现。
6、也可以有这样的需求,用户表加入是否在线的字段。
7、也可以有这样的需求,客服的前端页面会不同,因为客服与很多人对话,他得看的方便,所以得是一个人一个窗口这样。但这些都需要前端程序员的配合。