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、也可以有这样的需求,客服的前端页面会不同,因为客服与很多人对话,他得看的方便,所以得是一个人一个窗口这样。但这些都需要前端程序员的配合。