package auth import ( "errors" "net/http" "strings" "hfb_sys/backend/pkg/response" "github.com/gin-gonic/gin" ) type Handler struct { service *Service } type SendSMSRequest struct { Phone string `json:"phone" binding:"required"` CaptchaID string `json:"captcha_id" binding:"required"` CaptchaCode string `json:"captcha_code" binding:"required"` } type LoginRequest struct { Phone string `json:"phone" binding:"required"` Code string `json:"code" binding:"required,len=6"` } type RefreshRequest struct { RefreshToken string `json:"refresh_token" binding:"required"` } func NewHandler(service *Service) *Handler { return &Handler{service: service} } func (h *Handler) Captcha(c *gin.Context) { item, err := h.service.Captcha(c.Request.Context()) if err != nil { writeAuthError(c, err) return } response.OK(c, item) } // SendSMS 发送短信验证码 // @Summary 发送短信验证码 // @Description 发送登录验证码到指定手机号 // @Tags 认证 // @Accept json // @Produce json // @Param request body SendSMSRequest true "手机号" // @Success 200 {object} response.Body "发送成功" // @Failure 400 {object} response.Body "请求参数错误" // @Failure 500 {object} response.Body "服务器错误" // @Router /auth/sms/send [post] func (h *Handler) SendSMS(c *gin.Context) { var req SendSMSRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "手机号和图形验证码不能为空") return } if err := h.service.SendSMSCode(c.Request.Context(), strings.TrimSpace(req.Phone), strings.TrimSpace(req.CaptchaID), strings.TrimSpace(req.CaptchaCode)); err != nil { writeAuthError(c, err) return } response.OK(c, gin.H{"phone": PublicPhone(req.Phone), "expires_in": 300}) } // Login 短信验证码登录 // @Summary 短信验证码登录 // @Description 使用手机号和验证码登录 // @Tags 认证 // @Accept json // @Produce json // @Param request body LoginRequest true "登录信息" // @Success 200 {object} response.Body "登录成功,返回用户信息和 token" // @Failure 400 {object} response.Body "请求参数错误" // @Failure 401 {object} response.Body "验证码错误" // @Failure 500 {object} response.Body "服务器错误" // @Router /auth/sms/login [post] func (h *Handler) Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "手机号和验证码不能为空") return } result, err := h.service.LoginWithSMS(c.Request.Context(), strings.TrimSpace(req.Phone), strings.TrimSpace(req.Code)) if err != nil { writeAuthError(c, err) return } response.OK(c, result) } // Refresh 刷新访问令牌 // @Summary 刷新访问令牌 // @Description 使用 refresh_token 获取新的 access_token // @Tags 认证 // @Accept json // @Produce json // @Param request body RefreshRequest true "刷新令牌" // @Success 200 {object} response.Body "刷新成功,返回新的 token" // @Failure 400 {object} response.Body "请求参数错误" // @Failure 401 {object} response.Body "refresh_token 无效" // @Router /auth/refresh [post] func (h *Handler) Refresh(c *gin.Context) { var req RefreshRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "refresh_token 不能为空") return } tokens, err := h.service.RefreshToken(req.RefreshToken) if err != nil { response.Unauthorized(c, "刷新令牌无效或已过期") return } response.OK(c, tokens) } // Logout 登出 // @Summary 登出 // @Description 用户登出(客户端需清除本地 token) // @Tags 认证 // @Accept json // @Produce json // @Success 200 {object} response.Body "登出成功" // @Security BearerAuth // @Router /auth/logout [post] func (h *Handler) Logout(c *gin.Context) { response.OK(c, gin.H{"logged_out": true}) } func writeAuthError(c *gin.Context, err error) { switch { case errors.Is(err, ErrDependencyUnavailable): response.ServiceUnavailable(c, "数据库或 Redis 未连接") case errors.Is(err, ErrInvalidPhone): response.BadRequest(c, "手机号格式不正确") case errors.Is(err, ErrCaptchaInvalid): response.BadRequest(c, "图形验证码错误或已过期") case errors.Is(err, ErrCodeRateLimited): response.Error(c, http.StatusTooManyRequests, "rate_limited", "验证码发送过于频繁") case errors.Is(err, ErrSMSSendFailed): response.Error(c, http.StatusBadGateway, "sms_send_failed", "验证码发送失败,请稍后重试") case errors.Is(err, ErrCodeInvalid): response.BadRequest(c, "验证码错误或已过期") case errors.Is(err, ErrUserDisabled): response.Error(c, http.StatusForbidden, "user_disabled", "用户已被冻结") default: response.Error(c, http.StatusInternalServerError, "internal_error", "服务暂时不可用") } }