websocket分页上下文不可由客户端在url中传递page/size参数,必须使用服务端签发的一次性游标(如jwt加密的含user_id、table、ts字段的结构),连接后通过初始化消息发送,服务端解密验证权限并绑定lastsentid至内存,断线重连时从db查最新已推id续传,避免redis延迟和客户端篡改。

WebSocket连接中如何安全传递分页上下文
直接在 WebSocket URL query 中塞 page=1&size=20 是危险的——参数可被前端任意篡改,且无法校验用户是否有权访问该分页范围。真实场景下,分页上下文必须和服务端状态绑定,而不是靠客户端传。
推荐做法是:连接建立后,客户端先发一条初始化消息(如 {"type":"init","cursor":"abc123"}),其中 cursor 是服务端签发的一次性游标(例如 JWT 加密的 {user_id:123, table:"logs", ts:1724248560}),服务端解密并验证权限后,才允许后续滚动请求。
- 游标必须含时间戳或单调递增字段,防止重放
- 不要用
offset/limit做分页,改用基于游标的WHERE created_at - 游标有效期建议设为 5 分钟,超时需重新鉴权
GORM查询结果如何通过WebSocket实时推送给指定连接
不能每次查完都遍历所有连接广播。GORM 本身不感知 WebSocket,关键在于把数据库查询和连接管理解耦。
典型结构是:一个 map[uint64]*websocket.Conn 存活连接池(key 可用用户 ID 或会话 ID),配合 channel 做异步推送:
type Pusher struct {
connMap sync.Map // uint64 → *websocket.Conn
msgChan chan PushMsg
}
type PushMsg struct {
UserID uint64
Payload []byte
}
当 GORM 查出新数据后,只往 msgChan 发送结构体,由单独 goroutine 拉取、查 connMap、写 WebSocket —— 这样避免阻塞数据库事务。
- 查完 GORM 数据后立刻
rows.Close(),别等 WebSocket 写完 - 写 WebSocket 前务必检查
conn.IsClosed(),否则 panic - 对高并发推送,考虑用
sync.Pool复用[]byte缓冲区
滚动分页时如何避免重复推送或漏推
常见错误是:用户快速滑动,前端连续发多个 next 请求,后端没做去重或顺序控制,导致消息乱序或重复。
根本解法是把“滚动”变成服务端有状态的行为:每个连接维护一个 lastSentID uint64(比如 MySQL 的自增主键或 MongoDB 的 _id),下次查询强制加 WHERE id > lastSentID,查完立即更新该值。
- 这个
lastSentID必须存在内存里(如connMap对应 value 的 struct 字段),不能查 Redis,否则延迟不可控 - 若用户断线重连,需从上次
lastSentID继续,所以首次连接要从 DB 查最新已推 ID(如SELECT MAX(id) FROM events WHERE user_id = ?) - 不要依赖 WebSocket 的
onmessage顺序——TCP 层保证顺序,但应用层重连后可能丢状态
为什么 GORM 的 Rows.Scan 和 WebSocket WriteMessage 不能混在同一个 goroutine 里
因为 GORM 查询可能耗时(尤其带 JOIN 或未命中索引),而 WriteMessage 要求连接活跃且无阻塞。两者绑死会导致:一个慢查询拖垮整个连接的实时性,甚至触发 write deadline 超时断连。
必须拆开:goroutine A 负责 GORM 查询 + 构建 payload;goroutine B 负责监听 channel 并写 WebSocket。中间用带缓冲 channel 隔离(容量建议 1–3,防积压)。
- 缓冲太大会吃光内存,缓冲为 0 容易因写不过来导致 sender goroutine 阻塞
- 如果 payload 很大(>64KB),优先用
WriteMessage(websocket.BinaryMessage, ...),避免 UTF-8 校验开销 - 永远设置
conn.SetWriteDeadline(time.Now().Add(5 * time.Second)),不设就等于没保护
最易被忽略的是:滚动分页的“实时性”本质是“最终一致性”,不是强一致。用户滑动时看到的可能是 100ms 前的数据快照,只要不跳号、不重复、不卡死,就是可用的。硬要毫秒级精确,反而要把简单问题复杂化。











