Files
cloudnest/internal/application/email/service.go
T

145 lines
3.6 KiB
Go

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()
}