swoole 的练习 demo(6)- 数据库设计与实现

PHP技术
439
0
0
2022-11-14

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

截图