11.2.1.项目整体架构和技术选型及部署
11.2.1.项目整体架构和技术选型及部署
1 项目简介
支持HTTP请求,掌握HTTP API + json的请求相应
支持Websocket,掌握json做序列化和反序列化
支持多房间聊天 多个主题聊天
支持多人聊天
支持MySQL存储用户信息
支持Redis缓存token,存储聊天消息
2 项目架构
4.1 项目框架
4.2 聊天逻辑
4.3 数据存储
MySQL:存储用户信息,在0voice_chatroom数据库对应的users表。
Redis:存储房间消息和用户cookie
- 房间消息:使用redis的stream结构,key为房间id,value为房间的聊天消息
- 用户cookie,使用redis的string结构,key为cookie,value为用户id,cookie默认有效期是1天,超过1 天redis就将他删除,就需要用户重新登录。
4.4 消息格式
4.4.1 HTTP请求消息格式
create_account创建账号消息
API URL:http:xxx.xxx.xxx.xxx:3000/api/create-account
{
"username": "darren",
"email": "326873713@qq.com",
"password": "xxxxxxx"
}
login登录消息
API URL:http:xxx.xxx.xxx.xxx:3000/api/login
{
"email": "326873713@qq.com",
"password": "xxxxxxx"
}
4.4.2 Websocket交互消息格式
1 刚websocket连接的消息
服务器回应客户端的数据,拉取已有的聊天室以及对应的聊天消息。
{
"type": "hello",
"payload": {
"me": {
"id": 5,
"username": "小鸭子米奇"
},
"rooms": [
{
"id": "beast",
"name": "程序员老廖",
"hasMoreMessages": false,
"messages": [
{
"id": "1726840364728-0",
"content": "222222",
"user": {
"id": 5,
"username": "小鸭子米奇"
},
"timestamp": 1726840364726
},
{
"id": "1726840317055-0",
"content": "222",
"user": {
"id": 5,
"username": "小鸭子米奇"
},
"timestamp": 1726840317055
}
.......
]
},
{
"id": "async",
"name": "Boost.Async",
"hasMoreMessages": false,
"messages": [
{
"id": "1726839255147-0",
"content": "2",
"user": {
"id": 5,
"username": "小鸭子米奇"
},
"timestamp": 1726839255146
},
{
"id": "1726836482227-0",
"content": "22222222",
"user": {
"id": 5,
"username": "小鸭子米奇"
},
"timestamp": 1726836482218
}
]
},
{
"id": "db",
"name": "Database connectors",
"hasMoreMessages": false,
"messages": []
},
{
"id": "wasm",
"name": "Web assembly",
"hasMoreMessages": false,
"messages": []
}
]
}
}
2 聊天消息格式
发送端:比如用户名:小鸭子米奇,用户id:5发送的消息,此时会携带cookie
{
"type": "clientMessages",
"payload": {
"roomId": "beast",
"messages": [
{
"content": "这是小鸭子发送的消息"
}
]
}
}
经过服务端处理后转发给其他接收者的消息,此时消息类型type 变为“serverMessages”,message字段增 加了消息id,并增加了用户信息 “user”: { “id”: 5, “username”: “小鸭子米奇”},,以及时间戳timestamp。
{
"type": "serverMessages",
"payload": {
"roomId": "beast",
"messages": [
{
"id": "1726839290525-0",
"content": "这是小鸭子发送的消息",
"user": {
"id": 5,
"username": "小鸭子米奇"
},
"timestamp": 1726839290524
}
]
}
}
发送端的json数据只所以不带用户信息,是因为其可以通过cookie从redis读取email,再根据 email去 MySQL查询到username和user id,这里这个设计可以了解,但这种做法虽然减少了客户端发送的数据量。
3 获取历史消息
请求类型 type: “requestRoomHistory”,
结构:roomId 房间id, firstMessageId消息起始id
应答结构:
- roomId 房间id
- messages 对应的消息组
- hasMoreMessages 是否还有消息
- type 类型 roomHistory
redis演示:
输入消息:
127.0.0.1:6379> DEL mystream
127.0.0.1:6379> XADD mystream * value 0
1736936425901-0
127.0.0.1:6379> XADD mystream * value 1
1736936431129-0
127.0.0.1:6379> XADD mystream * value 2
1736936437028-0
127.0.0.1:6379> XADD mystream * value 3
1736936442697-0
127.0.0.1:6379> XADD mystream * value 4
1736936447601-0
127.0.0.1:6379> XADD mystream * value 5
1736936452204-0
127.0.0.1:6379> XADD mystream * value 6
1736936457168-0
127.0.0.1:6379> XADD mystream * value 7
1736936461668-0
127.0.0.1:6379> XADD mystream * value 8
1736936467004-0
127.0.0.1:6379> XADD mystream * value 9
1736936473984-0
127.0.0.1:6379> XADD mystream * value 10
1736936479128-0
127.0.0.1:6379> XADD mystream * value 11
1736936485528-0
127.0.0.1:6379>
分页读取,先读取最近的数据
1. 第一次拉取
XREVRANGE mystream + - COUNT 5
返回结果:
127.0.0.1:6379> XREVRANGE mystream + - COUNT 5
1736936485528-0
value
11
1736936479128-0
value
10
1736936473984-0
value
9
1736936467004-0
value
8
1736936461668-0
value
7
2. 第二次拉取
假设上次拉取的最后一条消息 ID 是 1736936461668-0 ,则下一次拉取的命令为:
127.0.0.1:6379> XREVRANGE mystream (1736936461668-0 - COUNT 5
1736936457168-0
value
6
1736936452204-0
value
5
1736936447601-0
value
4
1736936442697-0
value
3
1736936437028-0
value
2
3. 第三次拉取
假设上次拉取的最后一条消息 ID 是 1736936437028-0 ,则下一次拉取的命令为:
127.0.0.1:6379> XREVRANGE mystream (1736936437028-0 - COUNT 5
1736936431129-0
value
1
1736936425901-0
value
0
4 注意事项
1. 消息 ID 格式
Redis Stream 的消息 ID 是一个时间戳加序列号的形式,例如 1672531199000-0 。 Redis Stream 中的消息 ID 是一个特殊的格式,由两部分组成:时间戳和序列号,格式如下:
<毫秒时间戳>-<序列号>
例如,消息 ID 1736936425901-0 的格式解析如下:
- 时间戳部分 ( 1736936425901 )
这是一个以毫秒为单位的 Unix 时间戳。 1736936425901 表示从 1970 年 1 月 1 日 00:00:00 UTC 开始计算的毫秒数。 转换为人类可读的时间:
date -d @1736936425
输出:
Wed 15 Jan 2025 06:20:25 PM CST
- 序列号部分 ( 0 )
这是一个自增的序列号,用于区分同一毫秒内生成的多个消息。 如果在同一毫秒内添加了多条消息,Redis 会自动递增序列号。 例如:
第一条消息: 1736936425901-0 第二条消息: 1736936425901-1 第三条消息: 1736936425901-2
2. ( 符号的作用
在消息 ID 前加 ( 表示不包含该消息 ID,从下一条消息开始拉取。
3. 空结果
如果返回空结果,表示已经拉取完所有消息。
4. 性能
XREVRANGE 的性能较高,适合用于实时消息处理场景。
5. 可以自定义消息id
127.0.0.1:6379> DEL mystream
1
127.0.0.1:6379> XADD mystream 1 value 1
1-0
127.0.0.1:6379> XADD mystream 2 value 2
2-0
也可以手动指定消息 ID,但必须满足以下条件:
- 时间戳部分必须大于 Stream 中已有的最大时间戳。
- 序列号部分必须大于同一时间戳内的最大序列号。
Redis Stream 的消息 ID 格式为<毫秒时间戳>- <序列号>- ,用于唯一标识和排序消息。时间戳部分表示消息 的创建时间,序列号部分用于区分同一毫秒内的多条消息。
参考链接: