dockerfile修改
This commit is contained in:
@@ -6,23 +6,25 @@ import (
|
||||
|
||||
// Message 核心变更:增加 Room 字段,移除歧义字段
|
||||
type Message struct {
|
||||
Type string `json:"type"` // 消息类型(严格按常量)
|
||||
User string `json:"user"` // 发送者(客户端填,服务端可校验)
|
||||
Content string `json:"content"` // 消息内容
|
||||
Time string `json:"time"` // 服务端统一覆盖为 RFC3339(防客户端篡改)
|
||||
To string `json:"to,omitempty"` // 私聊目标(仅 type=private 时有效)
|
||||
Room string `json:"room,omitempty"` // 房间ID(仅 type=room 时有效)
|
||||
Type string `json:"type"` // 消息类型(严格按常量)
|
||||
User string `json:"user"` // 发送者(客户端填,服务端可校验)
|
||||
Content string `json:"content"` // 消息内容
|
||||
Time string `json:"time"` // 服务端统一覆盖为 RFC3339(防客户端篡改)
|
||||
To string `json:"to,omitempty"` // 私聊目标(仅 type=private 时有效)
|
||||
Room string `json:"room,omitempty"` // 房间ID(仅 type=room 时有效)
|
||||
Count int64 `json:"count,omitempty"` // 在线人数(仅 type=user_count 时有效)
|
||||
}
|
||||
|
||||
// 消息类型常量
|
||||
const (
|
||||
MsgTypeLogin = "login" // 服务端生成:用户上线事件
|
||||
MsgTypeLogout = "logout" // 服务端生成:用户下线事件
|
||||
MsgTypeBroadcast = "broadcast" // 全体用户广播(无视房间)
|
||||
MsgTypeRoom = "room" // 房间消息(必须带 Room 字段)
|
||||
MsgTypePrivate = "private" // 私聊(必须带 To 字段)
|
||||
MsgTypeSystem = "system" // 服务端生成:系统通知
|
||||
MsgTypeError = "error" // 服务端生成:定向错误提示
|
||||
MsgTypeLogin = "login" // 服务端生成:用户上线事件
|
||||
MsgTypeLogout = "logout" // 服务端生成:用户下线事件
|
||||
MsgTypeBroadcast = "broadcast" // 全体用户广播(无视房间)
|
||||
MsgTypeRoom = "room" // 房间消息(必须带 Room 字段)
|
||||
MsgTypePrivate = "private" // 私聊(必须带 To 字段)
|
||||
MsgTypeSystem = "system" // 服务端生成:系统通知
|
||||
MsgTypeError = "error" // 服务端生成:定向错误提示
|
||||
MsgTypeUserCount = "user_count" // 服务端生成:在线人数更新
|
||||
)
|
||||
|
||||
// 发送广播消息
|
||||
|
||||
56
internal/redis/client.go
Normal file
56
internal/redis/client.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const UserZSet = "chat:users"
|
||||
|
||||
type Client struct {
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
// NewClient 创建 Redis 客户端
|
||||
func NewClient(addr string, password string, db int) (*Client, error) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: password,
|
||||
DB: db,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("redis ping error: %w", err)
|
||||
}
|
||||
|
||||
return &Client{rdb: rdb}, nil
|
||||
}
|
||||
|
||||
// Close 关闭连接
|
||||
func (c *Client) Close() error {
|
||||
return c.rdb.Close()
|
||||
}
|
||||
|
||||
// AddUserToZSet 添加用户到 ZSet
|
||||
func (c *Client) AddUserToZSet(key string, user string, score int64) error {
|
||||
return c.rdb.ZAdd(context.Background(), key, redis.Z{
|
||||
Score: float64(score),
|
||||
Member: user,
|
||||
}).Err()
|
||||
}
|
||||
|
||||
// RemoveUserFromZSet 从 ZSet 移除用户
|
||||
func (c *Client) RemoveUserFromZSet(key string, user string) error {
|
||||
return c.rdb.ZRem(context.Background(), key, user).Err()
|
||||
}
|
||||
|
||||
// CountUsers 获取在线用户数
|
||||
func (c *Client) CountUsers(key string) (int64, error) {
|
||||
return c.rdb.ZCard(context.Background(), key).Result()
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package ws
|
||||
import (
|
||||
"ChatRoom/internal/models"
|
||||
"ChatRoom/internal/rabbitmq"
|
||||
"ChatRoom/internal/redis"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -30,14 +31,15 @@ var upgrader = websocket.Upgrader{
|
||||
|
||||
// 连接
|
||||
type Connection struct {
|
||||
wsConn *websocket.Conn //websocket连接
|
||||
rmqClient *rabbitmq.Client //rabbitmq客户端
|
||||
queueName string //队列名称
|
||||
userID string //用户ID
|
||||
send chan []byte //发送通道
|
||||
wsConn *websocket.Conn //websocket连接
|
||||
rmqClient *rabbitmq.Client //rabbitmq客户端
|
||||
redisClient *redis.Client //redis客户端
|
||||
queueName string //队列名称
|
||||
userID string //用户ID
|
||||
send chan []byte //发送通道
|
||||
}
|
||||
|
||||
func NewConnection(w http.ResponseWriter, r *http.Request, rmq *rabbitmq.Client) {
|
||||
func NewConnection(w http.ResponseWriter, r *http.Request, rmq *rabbitmq.Client, redisClient *redis.Client) {
|
||||
// 1. 升级 HTTP 到 WebSocket
|
||||
wsConn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
@@ -51,6 +53,13 @@ func NewConnection(w http.ResponseWriter, r *http.Request, rmq *rabbitmq.Client)
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
//redis记录用户
|
||||
if err := redisClient.AddUserToZSet(redis.UserZSet, userID, time.Now().Unix()); err != nil {
|
||||
log.Printf("Redis 用户记录失败: %v", err)
|
||||
wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(5000, "redis error"))
|
||||
wsConn.Close()
|
||||
return
|
||||
}
|
||||
// 3. 为用户创建 RabbitMQ 队列
|
||||
queueName, err := rmq.DeclareQueue()
|
||||
if err != nil {
|
||||
@@ -75,11 +84,12 @@ func NewConnection(w http.ResponseWriter, r *http.Request, rmq *rabbitmq.Client)
|
||||
|
||||
// 5. 创建 Connection 对象
|
||||
conn := &Connection{
|
||||
wsConn: wsConn,
|
||||
rmqClient: rmq,
|
||||
queueName: queueName,
|
||||
userID: userID,
|
||||
send: make(chan []byte, 256),
|
||||
wsConn: wsConn,
|
||||
rmqClient: rmq,
|
||||
redisClient: redisClient,
|
||||
queueName: queueName,
|
||||
userID: userID,
|
||||
send: make(chan []byte, 256),
|
||||
}
|
||||
|
||||
// 发送登录成功消息
|
||||
@@ -93,13 +103,20 @@ func NewConnection(w http.ResponseWriter, r *http.Request, rmq *rabbitmq.Client)
|
||||
conn.send <- data
|
||||
}
|
||||
|
||||
// 广播在线人数
|
||||
conn.broadcastUserCount()
|
||||
|
||||
go conn.writePump()
|
||||
go conn.readPump()
|
||||
go conn.consumeFromRabbitMQ()
|
||||
}
|
||||
|
||||
func (c *Connection) readPump() {
|
||||
defer c.wsConn.Close()
|
||||
defer func() {
|
||||
c.redisClient.RemoveUserFromZSet(redis.UserZSet, c.userID)
|
||||
c.broadcastUserCount()
|
||||
c.wsConn.Close()
|
||||
}()
|
||||
|
||||
c.wsConn.SetReadLimit(maxMessageSize)
|
||||
c.wsConn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
@@ -231,3 +248,24 @@ func (c *Connection) consumeFromRabbitMQ() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 广播在线人数
|
||||
func (c *Connection) broadcastUserCount() {
|
||||
count, err := c.redisClient.CountUsers(redis.UserZSet)
|
||||
if err != nil {
|
||||
log.Printf("获取在线人数失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &models.Message{
|
||||
Type: models.MsgTypeUserCount,
|
||||
User: "system",
|
||||
Count: count,
|
||||
Time: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(msg)
|
||||
if err := c.rmqClient.Publish(c.rmqClient.ExchangeName, "chat.global", body); err != nil {
|
||||
log.Printf("广播在线人数失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user