目录

11.2.1.项目整体架构和技术选型及部署

11.2.1.项目整体架构和技术选型及部署

1 项目简介

  1. 支持HTTP请求,掌握HTTP API + json的请求相应

  2. 支持Websocket,掌握json做序列化和反序列化

  3. 支持多房间聊天 多个主题聊天

  4. 支持多人聊天

  5. 支持MySQL存储用户信息

  6. 支持Redis缓存token,存储聊天消息

2 项目架构

4.1 项目框架

https://i-blog.csdnimg.cn/direct/4d7e4430c7274d99b88f1c0d86276e9b.png

4.2 聊天逻辑

https://i-blog.csdnimg.cn/direct/6d7eb107cf944a4db2fe91ea6cd0064e.png

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 的格式解析如下:

  1. 时间戳部分 ( 1736936425901 )

这是一个以毫秒为单位的 Unix 时间戳。 1736936425901 表示从 1970 年 1 月 1 日 00:00:00 UTC 开始计算的毫秒数。 转换为人类可读的时间:

date -d @1736936425

输出:

Wed 15 Jan 2025 06:20:25 PM CST
  1. 序列号部分 ( 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 格式为<毫秒时间戳>- <序列号>- ,用于唯一标识和排序消息。时间戳部分表示消息 的创建时间,序列号部分用于区分同一毫秒内的多条消息。

参考链接: