Files
backend/internal/handler/qrcode_handler.go
lafay 67a5660952
All checks were successful
Build Backend / build (push) Successful in 3m54s
Build Backend / build-docker (push) Successful in 1m9s
refactor: unify code formatting and improve push/search implementations
- Remove BOM from all Go source files
- Standardize import ordering and whitespace across handlers and services
- Replace PostgreSQL full-text search with ILIKE pattern matching in post and user repositories
- Enhance JPush client with TLS transport, rate limit monitoring, and simplified API
- Refactor PushService to include userID in device management methods
- Add MaxDevicesPerUser limit and extract helper functions for push payload construction
2026-04-28 14:53:04 +08:00

238 lines
5.4 KiB
Go

package handler
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"with_you/internal/pkg/response"
"with_you/internal/pkg/ws"
"with_you/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})
}