145 lines
3.6 KiB
Go
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()
|
|
}
|