### 效果图:
![聊天](https://box.kancloud.cn/2aa2836f74b34193f1392443f16d5e91_792x535.png)
### 功能:
- 好友私聊
- 多人群聊
- 添加好友
- 加入群组
- 离线消息
- 未读消息提醒
- 聊天对话窗口
### 流程:
- 客户端第一次连接服务端,服务端保存连接和uid映射关系
![客户端第一次连接服务端,服务端保存连接和uid映射关系](https://box.kancloud.cn/73150371a18a3dca2f01738fe89a1652_596x203.png)
- 主动发送消息给服务端,调用ws.send(),接受服务端发来消息时会触发onmessage事件
![主动发送消息给服务端,调用ws.send(),接受服务端发来消息时会触发onmessage事件](https://box.kancloud.cn/bc2482302eb56efb30aea7c9fdd6b7f6_472x341.png)
#### 客户端api
```javascript
var ws = new WebSocket();
ws.onopen() // 第一次连接服务端,会触发
ws.onmessage() // 接受到服务端消息会触发
ws.send() // 主动推送消息给服务端
ws.onclose() // 关闭客户端会触发
```
#### 服务端api
```php
$server = new swoole_websocket_server()
$server->on('open',functoin(){}); // 有客户端连接时,会触发
$server->on('message',function(){}) // 接受到客户端消息会触发
$server->on('close',function(){}) // 客户端关闭会触发,可以用来清除redis中fd和uid的映射关系
```
### 考虑的问题:
- 离线消息和聊天历史记录问题:
- 不在线的用户也可以收到消息,用户可以查看历史聊天记录,要实现这两个功能点必须要求消息持久化处理,可以保存到mysql,mongodb,file文件;
- 消息可以分为两种类型,一种是私聊,另一种是群聊,保存的时候要区分两者
- 保存聊天记录不影响我们的正常流程,所以可以用异步去处理数据,这样系统可以更快的响应客户端
- 保存用户uid和连接fd映射关系:
- 客户端每次打开新页面都会有产生一个新的连接fd,也就说一个uid会对应多个fd,发送给uid时,我们需要把消息推送到uid对应的每一个fd,这里用redis的集合来存储uid和fd关系
- 聊天对话:
- 聊天对话是相互的,例如a给b发消息,此时a和b拥有同个对话,我们a和b对话设置成一条记录,找出对话框时可以用or来处理,避免生成两条记录,同时这个对话ID可以作为消息记录外键,a和b都可以通过对话
ID查询到彼此的聊天记录
### 数据库设计:
> 针对上面问题,我主要设置了聊天对话表和聊天记录表
- 对话框表
### 表: chat_dialog 聊天对话表
| 字段 | 类型 | 备注 |
| :------: | :------: | :------: |
| id | int | 主键ID |
| from_uid | int | 对话发起者 |
| to_uid | int | 对话接受者 |
| group_id | int | 群组ID,默认为0 |
| type | tinyint | 对话类型,1表示人对人,2表示人对群 |
| status | tinyint | 状态,0正常,1关闭 |
获取某个人所有对话(包括人对人,人对群):
```sql
select * from chat_dialog where from_uid =1 or to_uid = 1;
```
> 说明下:例如a发消息给b,此时生成一条chat_dialog记录,from_uid是a,to_uid是b;需要获取a所有对话时,可以用select * from chat_dialog where from_uid = a or to_uid =a;
同理,需要获取b聊天对话时,可以执行select * from chat_dialog where from_uid = b or to_uid =b;这样双方都能获得同一个对话消息
### 表: chat_records 消息记录表
| 字段 | 类型 | 备注 |
| :------: | :------: | :------: |
| id | int | 主键ID |
| user_id | int | 消息发送者 |
| dialog_id | int | 对话ID,包括私聊和群聊 |
| group_id | int | 群组ID,默认为0 |
| status | tinyint | 状态,0正常,1关闭 |
获取私聊对话历史消息:
```sql
select * from chat_records where dialog_id = 1;
```
获取群聊历史消息
```sql
select * from chat_records where group_id = 1;
```