- 所有Handler方法使用标准{code:0,message:"success",data:...}响应格式
- 修复Cursor分页响应包装(GetAllDevices,GetLoginLogs,ListUsers等)
- 修复AuthHandler和SMSHandler认证方法响应格式
- 修复operation_log.go admin用户operation_type前缀问题
- 修复DashboardPage嵌套stats结构
- 修复LoginLogsPage reset功能stale closure问题
- 修复UsersPage批量操作API调用
- 修复多个前端测试(mock格式、按钮选择、断言逻辑)
- 添加OAuth测试域名白名单
- 新增代码审查流程文档
107 lines
2.8 KiB
Go
107 lines
2.8 KiB
Go
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
|
||
}
|
||
|
||
// 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 发送短信验证码(用于注册/登录)
|
||
func (h *SMSHandler) SendCode(c *gin.Context) {
|
||
if h.smsCodeService == nil {
|
||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "SMS service not configured"})
|
||
return
|
||
}
|
||
|
||
var req service.SendCodeRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": 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 短信验证码登录(带设备信息以支持设备信任链路)
|
||
func (h *SMSHandler) LoginByCode(c *gin.Context) {
|
||
if h.authService == nil {
|
||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "SMS login not configured"})
|
||
return
|
||
}
|
||
|
||
var req 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"`
|
||
}
|
||
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": 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,
|
||
})
|
||
}
|