package email import ( "context" "crypto/tls" "fmt" "math/rand" "net" "net/smtp" "time" "cloudnest/internal/config" "cloudnest/internal/infrastructure/database" "cloudnest/internal/pkg/logger" ) // EmailService provides email sending and verification code management. // EmailService 提供邮件发送和验证码管理功能。 type EmailService struct { cfg *config.EmailConfig redis *database.RedisClient } // NewEmailService creates a new EmailService. // NewEmailService 创建一个新的 EmailService。 func NewEmailService(cfg *config.EmailConfig, redis *database.RedisClient) *EmailService { return &EmailService{cfg: cfg, redis: redis} } // SendVerifyCode generates a 6-digit verification code, sends it via email, and stores it in Redis. // SendVerifyCode 生成6位数字验证码,通过邮件发送,并存入 Redis。 // // Returns: // - code: The generated verification code. // - err: Any error that occurred. func (s *EmailService) SendVerifyCode(toEmail string) (code string, err error) { if s.cfg.SMTPUsername == "" { return "", fmt.Errorf("邮件服务未配置") } code = generateCode(6) subject := "CloudNest 邮箱验证码" body := fmt.Sprintf("您的验证码是:%s,有效期10分钟,请勿泄露给他人。", code) if err = s.sendMail(toEmail, subject, body); err != nil { logger.Error("failed to send email", "error", err, "to", toEmail) return "", err } ctx := context.Background() key := fmt.Sprintf("email_code:%s", toEmail) if err = s.redis.Client.Set(ctx, key, code, 10*time.Minute).Err(); err != nil { return "", err } return code, nil } // VerifyCode checks the email verification code and deletes it from Redis on success. // VerifyCode 检查邮箱验证码,成功时从 Redis 中删除。 func (s *EmailService) VerifyCode(email, code string) bool { ctx := context.Background() key := fmt.Sprintf("email_code:%s", email) val, err := s.redis.Client.Get(ctx, key).Result() if err != nil { return false } if val != code { return false } s.redis.Client.Del(ctx, key) return true } func generateCode(length int) string { b := make([]byte, length) for i := range b { b[i] = byte(rand.Intn(10)) + '0' } return string(b) } func (s *EmailService) sendMail(to, subject, body string) error { from := s.cfg.SMTPUsername addr := fmt.Sprintf("%s:%d", s.cfg.SMTPHost, s.cfg.SMTPPort) msg := []byte(fmt.Sprintf("To: %s\r\nFrom: %s <%s>\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s", to, s.cfg.FromName, from, subject, body)) if s.cfg.SMTPPort == 587 { return sendMailSTARTTLS(addr, s.cfg.SMTPHost, from, s.cfg.SMTPPassword, []string{to}, msg) } auth := smtp.PlainAuth("", from, s.cfg.SMTPPassword, s.cfg.SMTPHost) return smtp.SendMail(addr, auth, from, []string{to}, msg) } func sendMailSTARTTLS(addr, host, from, password string, to []string, msg []byte) error { conn, err := net.Dial("tcp", addr) if err != nil { return err } defer conn.Close() client, err := smtp.NewClient(conn, host) if err != nil { return err } defer client.Close() tlsConfig := &tls.Config{ServerName: host} if err := client.StartTLS(tlsConfig); err != nil { return err } auth := smtp.PlainAuth("", from, password, host) if err := client.Auth(auth); err != nil { return err } if err := client.Mail(from); err != nil { return err } for _, addr := range to { if err := client.Rcpt(addr); err != nil { return err } } w, err := client.Data() if err != nil { return err } _, err = w.Write(msg) if err != nil { return err } err = w.Close() if err != nil { return err } return client.Quit() }