package handler import ( "net/http" "github.com/gin-gonic/gin" "github.com/user-management-system/internal/service" ) // SMSHandler handles SMS requests type SMSHandler struct { authService *service.AuthService smsCodeService *service.SMSCodeService } // SMSLoginRequest 短信登录请求 type SMSLoginRequest struct { Phone string `json:"phone" binding:"required"` Code string `json:"code" binding:"required"` DeviceID string `json:"device_id"` DeviceName string `json:"device_name"` DeviceBrowser string `json:"device_browser"` DeviceOS string `json:"device_os"` } // NewSMSHandler creates a new SMSHandler (stub, no SMS configured) func NewSMSHandler() *SMSHandler { return &SMSHandler{} } // NewSMSHandlerWithService creates a SMSHandler backed by real AuthService + SMSCodeService func NewSMSHandlerWithService(authService *service.AuthService, smsCodeService *service.SMSCodeService) *SMSHandler { return &SMSHandler{ authService: authService, smsCodeService: smsCodeService, } } // SendCode 发送短信验证码 // @Summary 发送短信验证码 // @Description 向指定手机号发送短信验证码(用于注册或登录) // @Tags 短信验证 // @Accept json // @Produce json // @Param request body service.SendCodeRequest true "发送验证码请求" // @Success 200 {object} Response "发送成功" // @Failure 400 {object} Response "请求参数错误" // @Failure 503 {object} Response "短信服务未配置" // @Router /api/v1/sms/send [post] func (h *SMSHandler) SendCode(c *gin.Context) { if h.smsCodeService == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": "SMS service not configured"}) return } var req service.SendCodeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } resp, err := h.smsCodeService.SendCode(c.Request.Context(), &req) if err != nil { handleError(c, err) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": resp, }) } // LoginByCode 短信验证码登录 // @Summary 短信验证码登录 // @Description 使用手机号和短信验证码登录(带设备信息以支持设备信任链路) // @Tags 短信验证 // @Accept json // @Produce json // @Param request body SMSLoginRequest true "登录请求" // @Success 200 {object} Response "登录成功" // @Failure 400 {object} Response "请求参数错误" // @Failure 401 {object} Response "验证码错误" // @Failure 503 {object} Response "短信登录未配置" // @Router /api/v1/sms/login [post] func (h *SMSHandler) LoginByCode(c *gin.Context) { if h.authService == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": "SMS login not configured"}) return } var req SMSLoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()}) return } clientIP := c.ClientIP() resp, err := h.authService.LoginByCode(c.Request.Context(), req.Phone, req.Code, clientIP) if err != nil { handleError(c, err) return } // 自动注册/更新设备记录(不阻塞主流程) // 注意:必须用独立的 background context,不能用 c.Request.Context()(gin 回收后会取消) if req.DeviceID != "" && resp != nil && resp.User != nil { loginReq := &service.LoginRequest{ DeviceID: req.DeviceID, DeviceName: req.DeviceName, DeviceBrowser: req.DeviceBrowser, DeviceOS: req.DeviceOS, } userID := resp.User.ID go func() { devCtx, cancel := newBackgroundCtx(5) defer cancel() h.authService.BestEffortRegisterDevicePublic(devCtx, userID, loginReq) }() } c.JSON(http.StatusOK, gin.H{ "code": 0, "message": "success", "data": resp, }) }