beta v0.2 dualcheck
This commit is contained in:
1 parent
c137299016
commit
1ec68bacf5
14 files changed
+1591
-359
No files matched your search
@@ -3,8 +3,11 @@ package dual_check
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
pkgws "cancerai.top/toolbox-server/internal/pkg/websocket"
|
||||
"cancerai.top/toolbox-server/internal/response"
|
||||
@@ -35,6 +38,7 @@ func (h *WSHandler) Handle(c *gin.Context) {
|
||||
sessionID := c.Query("session_id")
|
||||
role := c.Query("role")
|
||||
nickname := c.Query("nickname")
|
||||
browserID := c.Query("browser_id")
|
||||
|
||||
if sessionID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 4000, "message": "session_id required"})
|
||||
@@ -44,7 +48,7 @@ func (h *WSHandler) Handle(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 1004, "message": "role must be initiator or participant"})
|
||||
return
|
||||
}
|
||||
if nickname == "" || len(nickname) > 32 {
|
||||
if nickname == "" || utf8.RuneCountInString(nickname) > 32 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"code": 4000, "message": "nickname required (1-32)"})
|
||||
return
|
||||
}
|
||||
@@ -71,11 +75,27 @@ func (h *WSHandler) Handle(c *gin.Context) {
|
||||
}
|
||||
h.hub.Register(wsConn)
|
||||
|
||||
// 参与者首次连接:自动登记昵称(幂等)
|
||||
// 参与者首次连接:自动登记昵称(冲突时自动改名)
|
||||
actualNick := nickname
|
||||
if role == "participant" {
|
||||
_, _ = h.service.JoinParticipant(c.Request.Context(), sessionID, nickname)
|
||||
// 广播 participant_joined 给房间其他人
|
||||
h.hub.Broadcast(sessionID, response.WSParticipantJoined(nickname))
|
||||
_, actual, err := h.service.JoinParticipant(c.Request.Context(), sessionID, nickname, browserID)
|
||||
if err == nil {
|
||||
actualNick = actual
|
||||
if actualNick != nickname {
|
||||
// 自动改名:通知客户端 + 用 actualNick 广播
|
||||
wsConn.Send <- marshalMsg(response.WSNicknameAutoChanged(actualNick))
|
||||
h.logger.Info("ws participant auto-renamed",
|
||||
zap.String("session_id", truncateSessionID(sessionID)),
|
||||
zap.String("original", nickname),
|
||||
zap.String("actual", actualNick))
|
||||
}
|
||||
h.hub.Broadcast(sessionID, response.WSParticipantJoined(actualNick))
|
||||
} else {
|
||||
h.logger.Warn("ws JoinParticipant failed",
|
||||
zap.String("session_id", truncateSessionID(sessionID)),
|
||||
zap.String("nickname", nickname),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// 推一个 init 消息给新连接者,带上当前全量快照让它自己初始化 UI
|
||||
@@ -91,10 +111,13 @@ func (h *WSHandler) Handle(c *gin.Context) {
|
||||
|
||||
go pkgws.WritePump(wsConn)
|
||||
// ReadPump 里的 Unregister 会触发 participant_left 广播
|
||||
go h.readLoop(wsConn, nickname)
|
||||
go h.readLoop(wsConn, actualNick)
|
||||
}
|
||||
|
||||
func (h *WSHandler) readLoop(c *pkgws.Conn, nickname string) {
|
||||
func (h *WSHandler) readLoop(c *pkgws.Conn, initialNickname string) {
|
||||
// currentNick 是可变的,rename 成功后更新,保证 defer 中的 participant_left 广播用最新昵称
|
||||
currentNick := initialNickname
|
||||
|
||||
defer func() {
|
||||
c.WS.Close() // 先关,让 WritePump 也能退
|
||||
if c.Role == "initiator" {
|
||||
@@ -107,19 +130,87 @@ func (h *WSHandler) readLoop(c *pkgws.Conn, nickname string) {
|
||||
} else {
|
||||
h.logger.Info("initiator disconnected, session ended",
|
||||
zap.String("session_id", truncateSessionID(c.SessionID)),
|
||||
zap.String("nickname", nickname))
|
||||
zap.String("nickname", currentNick))
|
||||
}
|
||||
} else {
|
||||
// 参与者断开:广播 participant_left
|
||||
h.hub.Broadcast(c.SessionID, response.WSParticipantLeft(nickname))
|
||||
// 参与者断开:先从 Redis 删除字段,再广播 participant_left
|
||||
if err := h.service.RemoveParticipant(context.Background(), c.SessionID, currentNick); err != nil {
|
||||
h.logger.Warn("participant RemoveParticipant err",
|
||||
zap.String("session_id", truncateSessionID(c.SessionID)),
|
||||
zap.String("nickname", currentNick),
|
||||
zap.Error(err))
|
||||
}
|
||||
h.logger.Info("participant disconnected",
|
||||
zap.String("session_id", truncateSessionID(c.SessionID)),
|
||||
zap.String("nickname", currentNick))
|
||||
h.hub.Broadcast(c.SessionID, response.WSParticipantLeft(currentNick))
|
||||
}
|
||||
c.Hub.Unregister(c) // 最后从房间移除
|
||||
}()
|
||||
|
||||
c.WS.SetPongHandler(func(string) error { return nil })
|
||||
for {
|
||||
if _, _, err := c.WS.ReadMessage(); err != nil {
|
||||
_, raw, err := c.WS.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 解析客户端发来的消息
|
||||
var msg struct {
|
||||
Type string `json:"type"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &msg); err != nil {
|
||||
continue // 不是合法 JSON,忽略
|
||||
}
|
||||
|
||||
switch msg.Type {
|
||||
case "rename":
|
||||
// 只有参与者允许改昵称
|
||||
if c.Role != "participant" {
|
||||
h.sendError(c, "rename not allowed for role "+c.Role)
|
||||
continue
|
||||
}
|
||||
var data struct {
|
||||
NewNickname string `json:"new_nickname"`
|
||||
}
|
||||
if err := json.Unmarshal(msg.Data, &data); err != nil || strings.TrimSpace(data.NewNickname) == "" || utf8.RuneCountInString(data.NewNickname) > 32 {
|
||||
h.sendError(c, "invalid new_nickname")
|
||||
continue
|
||||
}
|
||||
newNick := strings.TrimSpace(data.NewNickname)
|
||||
if newNick == currentNick {
|
||||
continue // 无变化
|
||||
}
|
||||
err := h.service.UpdateParticipantNickname(context.Background(), c.SessionID, currentNick, newNick)
|
||||
if err != nil {
|
||||
var be *BusinessError
|
||||
if errors.As(err, &be) {
|
||||
h.sendError(c, be.Error()) // 优先用自定义 Message,否则回退到 ErrMessage(code)
|
||||
} else {
|
||||
h.sendError(c, "rename failed: "+err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 成功:更新本地昵称 + 广播给房间其他人
|
||||
oldNick := currentNick
|
||||
currentNick = newNick
|
||||
h.hub.Broadcast(c.SessionID, response.WSParticipantRenamed(oldNick, newNick))
|
||||
h.logger.Info("ws rename success",
|
||||
zap.String("session_id", truncateSessionID(c.SessionID)),
|
||||
zap.String("old", oldNick),
|
||||
zap.String("new", newNick),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendError 通过 WS 给客户端发送一个 error 消息
|
||||
func (h *WSHandler) sendError(c *pkgws.Conn, errMsg string) {
|
||||
msg := response.WebSocketMessage{Type: "error", Error: errMsg}
|
||||
select {
|
||||
case c.Send <- marshalMsg(msg):
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user