Files
user-system/internal/api/handler/sms_handler.go
long-agent 5ca3633be4 feat: 系统全面优化 - 设备管理/登录日志导出/性能监控/设置页面
后端:
- 新增全局设备管理 API(DeviceHandler.GetAllDevices)
- 新增登录日志导出功能(LogHandler.ExportLoginLogs, CSV/XLSX)
- 新增设置服务(SettingsService)和设置页面 API
- 设备管理支持多条件筛选(状态/信任状态/关键词)
- 登录日志支持流式导出防 OOM
- 操作日志支持按方法/时间范围搜索
- 主题配置服务(ThemeService)
- 增强监控健康检查(Prometheus metrics + SLO)
- 移除旧 ratelimit.go(已迁移至 robustness)
- 修复 SocialAccount NULL 扫描问题
- 新增 API 契约测试、Handler 测试、Settings 测试

前端:
- 新增管理员设备管理页面(DevicesPage)
- 新增管理员登录日志导出功能
- 新增系统设置页面(SettingsPage)
- 设备管理支持筛选和分页
- 增强 HTTP 响应类型

测试:
- 业务逻辑测试 68 个(含并发 CONC_001~003)
- 规模测试 16 个(P99 百分位统计)
- E2E 测试、集成测试、契约测试
- 性能基准测试、鲁棒性测试

全面测试通过(38 个测试包)
2026-04-07 12:08:16 +08:00

99 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, 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, resp)
}