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,6 +3,7 @@ package dual_check
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
pkgws "cancerai.top/toolbox-server/internal/pkg/websocket"
|
||||
@@ -38,7 +39,11 @@ func (h *Handler) Create(c *gin.Context) {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
resp, err := h.service.CreateSession(c.Request.Context(), req.Nickname)
|
||||
if err := validateNickname(req.Nickname); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
resp, err := h.service.CreateSession(c.Request.Context(), req.Nickname, req.BrowserID)
|
||||
if err != nil {
|
||||
var be *BusinessError
|
||||
if errors.As(err, &be) {
|
||||
@@ -64,6 +69,10 @@ func (h *Handler) Submit(c *gin.Context) {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
if err := validateNickname(req.Nickname); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
if utf8.RuneCountInString(req.Text) > h.maxLen {
|
||||
response.BadRequest(c, "text too long")
|
||||
return
|
||||
@@ -82,37 +91,33 @@ func (h *Handler) Submit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 广播事件到房间内所有连接(包括发起方和其他参与者)
|
||||
// 广播事件到房间内所有连接
|
||||
switch req.Role {
|
||||
case "initiator":
|
||||
// 发起方刚提交:触发全量快照推送(所有参与者都需要更新)
|
||||
sess, _ := h.service.GetSession(c.Request.Context(), req.SessionID)
|
||||
if sess != nil {
|
||||
h.hub.Broadcast(req.SessionID, response.WSFullState(sess))
|
||||
}
|
||||
case "participant":
|
||||
// 参与者刚提交:推送 participant_submitted 事件
|
||||
// 发起方需要更新该参与者的提交状态 + 可能的 match 结果
|
||||
h.hub.Broadcast(req.SessionID, response.WSParticipantSubmitted(req.Nickname, resp))
|
||||
}
|
||||
|
||||
response.Success(c, resp)
|
||||
}
|
||||
|
||||
// Join POST /api/v1/dual-check/join — 参与者加入(登记昵称)
|
||||
// Join POST /api/v1/dual-check/join
|
||||
func (h *Handler) Join(c *gin.Context) {
|
||||
var req JoinRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
sessionID := c.Param("session_id")
|
||||
if sessionID == "" {
|
||||
response.BadRequest(c, "session_id is required")
|
||||
if err := validateNickname(req.Nickname); err != nil {
|
||||
response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
sess, err := h.service.JoinParticipant(c.Request.Context(), sessionID, req.Nickname)
|
||||
sess, actualNick, err := h.service.JoinParticipant(c.Request.Context(), req.SessionID, req.Nickname, req.BrowserID)
|
||||
if err != nil {
|
||||
var be *BusinessError
|
||||
if errors.As(err, &be) {
|
||||
@@ -124,16 +129,27 @@ func (h *Handler) Join(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 广播 participant_joined 事件
|
||||
h.hub.Broadcast(sessionID, response.WSParticipantJoined(req.Nickname))
|
||||
h.hub.Broadcast(req.SessionID, response.WSParticipantJoined(actualNick))
|
||||
|
||||
if actualNick != req.Nickname {
|
||||
response.Success(c, gin.H{
|
||||
"session": sess,
|
||||
"actual_nickname": actualNick,
|
||||
"renamed": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
response.Success(c, sess)
|
||||
}
|
||||
|
||||
// GetSession GET /api/v1/dual-check/session/:session_id
|
||||
// GetSession POST /api/v1/dual-check/session
|
||||
func (h *Handler) GetSession(c *gin.Context) {
|
||||
sessionID := c.Param("session_id")
|
||||
sess, err := h.service.GetSession(c.Request.Context(), sessionID)
|
||||
var req SessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
sess, err := h.service.GetSession(c.Request.Context(), req.SessionID)
|
||||
if err != nil {
|
||||
var be *BusinessError
|
||||
if errors.As(err, &be) && be.Code == ErrCodeSessionNotFound {
|
||||
@@ -146,11 +162,14 @@ func (h *Handler) GetSession(c *gin.Context) {
|
||||
response.Success(c, sess)
|
||||
}
|
||||
|
||||
// CheckSession GET /api/v1/dual-check/check/:session_id
|
||||
// 前端加入前预校验
|
||||
// CheckSession POST /api/v1/dual-check/check
|
||||
func (h *Handler) CheckSession(c *gin.Context) {
|
||||
sessionID := c.Param("session_id")
|
||||
info, err := h.service.CheckSession(c.Request.Context(), sessionID)
|
||||
var req SessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
info, err := h.service.CheckSession(c.Request.Context(), req.SessionID)
|
||||
if err != nil {
|
||||
response.InternalError(c, "check session failed")
|
||||
return
|
||||
@@ -162,21 +181,15 @@ func (h *Handler) CheckSession(c *gin.Context) {
|
||||
response.Success(c, info)
|
||||
}
|
||||
|
||||
// SetResubmit POST /api/v1/dual-check/session/:session_id/resubmit
|
||||
// 发起方切换"允许参与者重新提交"开关
|
||||
// SetResubmit POST /api/v1/dual-check/resubmit
|
||||
func (h *Handler) SetResubmit(c *gin.Context) {
|
||||
sessionID := c.Param("session_id")
|
||||
if sessionID == "" {
|
||||
response.BadRequest(c, "session_id is required")
|
||||
return
|
||||
}
|
||||
var req SetResubmitRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
|
||||
sess, err := h.service.SetResubmit(c.Request.Context(), sessionID, req.Allow)
|
||||
sess, err := h.service.SetResubmit(c.Request.Context(), req.SessionID, req.Allow)
|
||||
if err != nil {
|
||||
var be *BusinessError
|
||||
if errors.As(err, &be) {
|
||||
@@ -188,26 +201,49 @@ func (h *Handler) SetResubmit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 广播开关变化给所有参与者
|
||||
h.hub.Broadcast(sessionID, response.WSResubmitChanged(req.Allow))
|
||||
h.hub.Broadcast(req.SessionID, response.WSResubmitChanged(req.Allow))
|
||||
|
||||
response.Success(c, sess)
|
||||
}
|
||||
|
||||
// EndSession POST /api/v1/dual-check/session/:session_id/end
|
||||
// 发起方主动结束会话:先广播 session_ended,再 Delete Redis key
|
||||
// SetIgnoreWs POST /api/v1/dual-check/ignore-ws
|
||||
func (h *Handler) SetIgnoreWs(c *gin.Context) {
|
||||
var req SetIgnoreWsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
|
||||
sess, err := h.service.SetIgnoreWs(c.Request.Context(), req.SessionID, req.Ignore)
|
||||
if err != nil {
|
||||
var be *BusinessError
|
||||
if errors.As(err, &be) {
|
||||
response.Fail(c, be.Code, be.Error())
|
||||
return
|
||||
}
|
||||
h.logger.Error("set ignore_ws error", zap.Error(err))
|
||||
response.InternalError(c, "set ignore_ws failed")
|
||||
return
|
||||
}
|
||||
|
||||
h.hub.Broadcast(req.SessionID, response.WSIgnoreWsChanged(req.Ignore))
|
||||
|
||||
response.Success(c, sess)
|
||||
}
|
||||
|
||||
// EndSession POST /api/v1/dual-check/end
|
||||
func (h *Handler) EndSession(c *gin.Context) {
|
||||
sessionID := c.Param("session_id")
|
||||
if sessionID == "" {
|
||||
response.BadRequest(c, "session_id is required")
|
||||
var req SessionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, ErrMessage(ErrCodeInvalidParam))
|
||||
return
|
||||
}
|
||||
|
||||
// 先广播,让所有参与方 UI 都弹提示
|
||||
h.hub.Broadcast(sessionID, response.WSSessionEnded())
|
||||
h.hub.Broadcast(req.SessionID, response.WSSessionEnded())
|
||||
|
||||
// 再删除数据
|
||||
if err := h.service.EndSession(c.Request.Context(), sessionID); err != nil {
|
||||
if err := h.service.EndSession(c.Request.Context(), req.SessionID); err != nil {
|
||||
h.logger.Error("end session error", zap.Error(err))
|
||||
response.InternalError(c, "end session failed")
|
||||
return
|
||||
@@ -216,5 +252,16 @@ func (h *Handler) EndSession(c *gin.Context) {
|
||||
response.Success(c, gin.H{"ended": true})
|
||||
}
|
||||
|
||||
// 占位,避免 http.StatusOK 未使用
|
||||
// validateNickname 统一的昵称校验:字符数 1-32(按 Unicode rune)
|
||||
func validateNickname(n string) error {
|
||||
n = strings.TrimSpace(n)
|
||||
if utf8.RuneCountInString(n) == 0 {
|
||||
return errors.New("nickname is required")
|
||||
}
|
||||
if utf8.RuneCountInString(n) > 32 {
|
||||
return errors.New("nickname must be 1-32 characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ = http.StatusOK
|
||||
Reference in new issue
Block a user