package handler import ( "crypto/rand" "encoding/hex" "fmt" "net/http" "time" "api-server/internal/auth" "api-server/internal/db" "api-server/internal/email" "github.com/gin-gonic/gin" ) type SettingsHandler struct { db *db.DB smtp *email.Sender } func NewSettingsHandler(database *db.DB, smtp *email.Sender) *SettingsHandler { return &SettingsHandler{db: database, smtp: smtp} } func (h *SettingsHandler) RegisterRoutes(r *gin.RouterGroup) { // 管理员:系统设置 admin := r.Group("/admin") admin.Use(auth.RequireAuth(h.db), auth.RequireAdmin()) admin.GET("/settings", h.GetSettings) admin.PUT("/settings", h.UpdateSettings) admin.POST("/settings/test-email", h.TestEmail) // 公开:忘记密码 / 重置密码 / 验证邮箱 r.POST("/auth/forgot-password", h.ForgotPassword) r.POST("/auth/reset-password", h.ResetPassword) r.POST("/auth/verify-email", h.VerifyEmail) // 登录用户:发送验证邮件 / 修改邮箱 authed := r.Group("") authed.Use(auth.RequireAuth(h.db)) authed.POST("/auth/send-verification", h.SendVerification) authed.PUT("/auth/email", h.UpdateEmail) } func (h *SettingsHandler) GetSettings(c *gin.Context) { cfg, err := h.db.GetSystemSettings() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": cfg}) } func (h *SettingsHandler) UpdateSettings(c *gin.Context) { var req map[string]string if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"}) return } for k, v := range req { if err := h.db.SetSystemSetting(k, v); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": fmt.Sprintf("设置 %s 失败: %s", k, err.Error())}) return } } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "已保存"}) } func (h *SettingsHandler) TestEmail(c *gin.Context) { var req struct { To string `json:"to" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供收件地址"}) return } if !h.smtp.Enabled() { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "SMTP 未配置"}) return } if err := h.smtp.Send(req.To, "测试邮件", "
这是一封测试邮件,SMTP 配置正常。
"); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": fmt.Sprintf("发送失败: %s", err.Error())}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "发送成功"}) } func (h *SettingsHandler) ForgotPassword(c *gin.Context) { var req struct { Email string `json:"email" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供邮箱"}) return } user, err := h.db.GetUserByEmail(req.Email) if err != nil { // 不暴露邮箱是否存在 c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "如果该邮箱已注册,将收到重置邮件"}) return } if !h.smtp.Enabled() { c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "如果该邮箱已注册,将收到重置邮件"}) return } token, err := generateToken() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "生成令牌失败"}) return } if err := h.db.CreateResetToken(user.ID, token, time.Now().Add(1*time.Hour)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "创建令牌失败"}) return } cfg, _ := h.db.GetSystemSettings() siteName := cfg["site_name"] if siteName == "" { siteName = "API Platform" } body := fmt.Sprintf(`你请求重置 %s 的密码:
或复制以下链接:
%s
链接有效期为 1 小时。
`, siteName, token, token) h.smtp.Send(req.Email, fmt.Sprintf("[%s] 密码重置", siteName), body) c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "如果该邮箱已注册,将收到重置邮件"}) } func (h *SettingsHandler) ResetPassword(c *gin.Context) { var req struct { Token string `json:"token" binding:"required"` NewPassword string `json:"new_password" binding:"required,min=6"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供令牌和新密码(至少6位)"}) return } userID, err := h.db.GetResetTokenByToken(req.Token) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "令牌无效或已过期"}) return } hash, err := auth.HashPassword(req.NewPassword) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "密码加密失败"}) return } user, err := h.db.GetUserByID(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "用户不存在"}) return } if err := h.db.UpdatePassword(user.Email, hash); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } h.db.DeleteResetToken(req.Token) c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "密码已重置,请重新登录"}) } func (h *SettingsHandler) SendVerification(c *gin.Context) { user := auth.GetUser(c) if !h.smtp.Enabled() { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "邮件服务未配置"}) return } token, err := generateToken() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "生成令牌失败"}) return } if err := h.db.CreateVerificationToken(user.ID, user.Email, token, time.Now().Add(1*time.Hour)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "创建令牌失败"}) return } cfg, _ := h.db.GetSystemSettings() siteName := cfg["site_name"] if siteName == "" { siteName = "API Platform" } body := fmt.Sprintf(`请点击以下链接验证你的邮箱:
令牌:%s
链接有效期为 1 小时。
`, token, token) h.smtp.Send(user.Email, fmt.Sprintf("[%s] 邮箱验证", siteName), body) c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "验证邮件已发送"}) } func (h *SettingsHandler) VerifyEmail(c *gin.Context) { var req struct { Token string `json:"token" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供验证令牌"}) return } userID, email, err := h.db.GetVerificationToken(req.Token) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "令牌无效或已过期"}) return } // 更新邮箱已验证状态 if err := h.db.UpdateEmailVerified(userID, true); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } h.db.DeleteVerificationToken(req.Token) c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "邮箱已验证", "data": gin.H{"email": email}}) } func (h *SettingsHandler) UpdateEmail(c *gin.Context) { user := auth.GetUser(c) var req struct { Email string `json:"email" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请提供新邮箱"}) return } if err := h.db.UpdateUserEmail(user.ID, req.Email); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } // 如果有 SMTP 配置,发送验证邮件 if h.smtp.Enabled() { token, err := generateToken() if err == nil { h.db.CreateVerificationToken(user.ID, req.Email, token, time.Now().Add(1*time.Hour)) cfg, _ := h.db.GetSystemSettings() siteName := cfg["site_name"] if siteName == "" { siteName = "API Platform" } body := fmt.Sprintf(`请点击以下链接验证你的新邮箱:
`, token) h.smtp.Send(req.Email, fmt.Sprintf("[%s] 邮箱验证", siteName), body) } } updated, err := h.db.GetUserByID(user.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "邮箱已修改", "data": userResponse(updated)}) } func generateToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil }