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
72. 第二次拉取
假设上次拉取的最后一条消息 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
23. 第三次拉取
假设上次拉取的最后一条消息 ID 是 1736936437028-0 ,则下一次拉取的命令为:
127.0.0.1:6379> XREVRANGE mystream (1736936437028-0 - COUNT 5
1736936431129-0
value
1
1736936425901-0
value
04 注意事项
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 格式为<毫秒时间戳>- <序列号>- ,用于唯一标识和排序消息。时间戳部分表示消息 的创建时间,序列号部分用于区分同一毫秒内的多条消息。
参考链接: