package handler import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" "carrot_bbs/internal/pkg/response" "carrot_bbs/internal/pkg/ws" "carrot_bbs/internal/service" ) // QRCodeHandler 二维码登录处理器 type QRCodeHandler struct { qrcodeService *service.QRCodeLoginService } // NewQRCodeHandler 创建二维码登录处理器 func NewQRCodeHandler(qrcodeService *service.QRCodeLoginService) *QRCodeHandler { return &QRCodeHandler{ qrcodeService: qrcodeService, } } // GetQRCode 获取二维码 // GET /api/v1/auth/qrcode func (h *QRCodeHandler) GetQRCode(c *gin.Context) { session, err := h.qrcodeService.CreateSession(c.Request.Context()) if err != nil { response.InternalServerError(c, "failed to create qrcode session") return } // 生成二维码URL (deeplink格式) qrcodeURL := fmt.Sprintf("carrotbbs://qrcode/login?session_id=%s", session.SessionID) response.Success(c, gin.H{ "session_id": session.SessionID, "qrcode_url": qrcodeURL, "expires_in": 300, // 5分钟 "expires_at": session.ExpiresAt, }) } // WSEvents WebSocket事件流 // GET /api/v1/auth/qrcode/events?session_id=xxx func (h *QRCodeHandler) WSEvents(c *gin.Context) { sessionID := c.Query("session_id") if sessionID == "" { response.BadRequest(c, "session_id is required") return } ch, cancel, replay := h.qrcodeService.GetWSHub().Subscribe(sessionID, 0) defer cancel() w := c.Writer flusher, ok := w.(http.Flusher) if !ok { response.InternalServerError(c, "streaming unsupported") return } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") w.Header().Set("X-Accel-Buffering", "no") c.Status(http.StatusOK) flusher.Flush() writeEvent := func(ev ws.Event) bool { data, err := ws.EncodeData(ev) if err != nil { return false } if _, err := fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", ev.ID, ev.Event, data); err != nil { return false } flusher.Flush() return true } // 发送历史事件 for _, ev := range replay { if !writeEvent(ev) { return } } // 心跳 heartbeat := time.NewTicker(25 * time.Second) defer heartbeat.Stop() for { select { case <-c.Request.Context().Done(): return case ev, ok := <-ch: if !ok || !writeEvent(ev) { return } case <-heartbeat.C: if _, err := fmt.Fprint(w, "event: heartbeat\ndata: {}\n\n"); err != nil { return } flusher.Flush() } } } // Scan 扫描二维码 // POST /api/v1/auth/qrcode/scan func (h *QRCodeHandler) Scan(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { response.Unauthorized(c, "") return } type ScanRequest struct { SessionID string `json:"session_id" binding:"required"` } var req ScanRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.qrcodeService.Scan(c.Request.Context(), req.SessionID, userID); err != nil { if err.Error() == "qrcode already scanned" { response.BadRequest(c, "qrcode already scanned by others") return } if err.Error() == "session not found or expired" { response.NotFound(c, "qrcode expired") return } response.InternalServerError(c, "failed to scan qrcode") return } // 获取用户信息返回 user, err := h.qrcodeService.GetUserService().GetUserByID(c.Request.Context(), userID) if err != nil { response.InternalServerError(c, "failed to get user info") return } response.Success(c, gin.H{ "user": gin.H{ "id": user.ID, "nickname": user.Nickname, "avatar": user.Avatar, }, }) } // Confirm 确认登录 // POST /api/v1/auth/qrcode/confirm func (h *QRCodeHandler) Confirm(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { response.Unauthorized(c, "") return } type ConfirmRequest struct { SessionID string `json:"session_id" binding:"required"` } var req ConfirmRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } ip := c.ClientIP() userAgent := c.GetHeader("User-Agent") result, err := h.qrcodeService.Confirm(c.Request.Context(), req.SessionID, userID, ip, userAgent) if err != nil { if err.Error() == "unauthorized" { response.Unauthorized(c, "not authorized to confirm this login") return } if err.Error() == "session not found or expired" { response.NotFound(c, "qrcode expired") return } response.InternalServerError(c, "failed to confirm login") return } response.Success(c, gin.H{ "success": true, "user": gin.H{ "id": result.User.ID, "username": result.User.Username, "nickname": result.User.Nickname, "avatar": result.User.Avatar, }, }) } // Cancel 取消登录 // POST /api/v1/auth/qrcode/cancel func (h *QRCodeHandler) Cancel(c *gin.Context) { userID := c.GetString("user_id") if userID == "" { response.Unauthorized(c, "") return } type CancelRequest struct { SessionID string `json:"session_id" binding:"required"` } var req CancelRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.qrcodeService.Cancel(c.Request.Context(), req.SessionID, userID); err != nil { if err.Error() == "unauthorized" { response.Unauthorized(c, "not authorized to cancel this login") return } response.InternalServerError(c, "failed to cancel login") return } response.Success(c, gin.H{"success": true}) }