11.2.1.项目整体架构和技术选型及部署
1 项目简介
1. 支持HTTP请求,掌握HTTP API + json的请求相应
2. 支持Websocket,掌握json做序列化和反序列化
3. 支持多房间聊天 多个主题聊天
4. 支持多人聊天
5. 支持MySQL存储用户信息
6. 支持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 mystream127.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 的格式解析如下:
1. 时间戳部分 ( 1736936425901 )
这是一个以毫秒为单位的 Unix 时间戳。 1736936425901 表示从 1970 年 1 月 1 日 00:00:00 UTC 开始计算的毫秒数。 转换为人类可读的时间:
date -d @1736936425
输出:
Wed 15 Jan 2025 06:20:25 PM CST
2. 序列号部分 ( 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 格式为<毫秒时间戳>- <序列号>- ,用于唯一标识和排序消息。时间戳部分表示消息 的创建时间,序列号部分用于区分同一毫秒内的多条消息。
参考链接:0voice · GitHub