package handler import ( "errors" "net/http" "github.com/gin-gonic/gin" "github.com/wangjia/jiu/backend/internal/middleware" "github.com/wangjia/jiu/backend/internal/service" "github.com/wangjia/jiu/backend/internal/util" ) type AuthHandler struct { svc *service.AuthService } func NewAuthHandler(svc *service.AuthService) *AuthHandler { return &AuthHandler{svc: svc} } // Login POST /api/v1/auth/login func (h *AuthHandler) Login(c *gin.Context) { var req struct { ShopCode string `json:"shop_code" binding:"required"` Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` DeviceID string `json:"device_id"` DeviceName string `json:"device_name"` Platform string `json:"platform"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } dev := service.DeviceInfo{ DeviceID: req.DeviceID, DeviceName: req.DeviceName, Platform: req.Platform, IP: c.ClientIP(), UserAgent: c.Request.UserAgent(), } pair, user, err := h.svc.Login(req.ShopCode, req.Username, req.Password, dev) if err != nil { status := http.StatusUnauthorized if errors.Is(err, service.ErrTooManyAttempts) { status = http.StatusTooManyRequests } else if errors.Is(err, service.ErrPlatformNotAllowed) { status = http.StatusForbidden } c.JSON(status, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "data": gin.H{ "access_token": pair.AccessToken, "refresh_token": pair.RefreshToken, "expires_in": pair.ExpiresIn, "shop_id": pair.ShopID, "user": gin.H{ "id": user.ID, "username": user.Username, "real_name": user.RealName, "role": user.Role, }, }, }) } // Register POST /api/v1/public/register func (h *AuthHandler) Register(c *gin.Context) { var req service.RegisterInput if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } result, err := h.svc.Register(req) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } util.RespondSuccess(c, result) } // Refresh POST /api/v1/auth/refresh func (h *AuthHandler) Refresh(c *gin.Context) { var req struct { RefreshToken string `json:"refresh_token" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } pair, err := h.svc.RefreshTokens(req.RefreshToken) if err != nil { // 被踢/登出/禁用 → 给前端明确 code,便于提示"账号已在其他设备登录/已失效" if errors.Is(err, service.ErrSessionRevoked) || errors.Is(err, service.ErrUserInactive) { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error(), "code": "SESSION_REVOKED"}) return } c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } util.RespondSuccess(c, pair) } // Logout POST /api/v1/auth/logout —— 撤销当前会话 func (h *AuthHandler) Logout(c *gin.Context) { if err := h.svc.Logout(middleware.GetSID(c)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } util.RespondSuccess(c, gin.H{"ok": true}) } // Ping POST /api/v1/auth/ping —— 心跳,仅触发中间件刷新 last_seen;会话已撤销则中间件直接 401 func (h *AuthHandler) Ping(c *gin.Context) { util.RespondSuccess(c, gin.H{"ok": true}) }