Files
backend/pkg/email/email.go
lan 4b4980820f
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Test / test (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / build (push) Has been cancelled
chore: 初始化仓库,排除二进制文件和覆盖率文件
2025-11-28 23:30:49 +08:00

163 lines
4.8 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 email
import (
"crypto/tls"
"fmt"
"net/smtp"
"net/textproto"
"carrotskin/pkg/config"
"github.com/jordan-wright/email"
"go.uber.org/zap"
)
// Service 邮件服务
type Service struct {
cfg config.EmailConfig
logger *zap.Logger
}
// NewService 创建邮件服务
func NewService(cfg config.EmailConfig, logger *zap.Logger) *Service {
return &Service{
cfg: cfg,
logger: logger,
}
}
// SendVerificationCode 发送验证码邮件
func (s *Service) SendVerificationCode(to, code, purpose string) error {
if !s.cfg.Enabled {
s.logger.Warn("邮件服务未启用,跳过发送", zap.String("to", to))
return fmt.Errorf("邮件服务未启用")
}
subject := s.getSubject(purpose)
body := s.getBody(code, purpose)
return s.send([]string{to}, subject, body)
}
// SendResetPassword 发送重置密码邮件
func (s *Service) SendResetPassword(to, code string) error {
return s.SendVerificationCode(to, code, "reset_password")
}
// SendEmailVerification 发送邮箱验证邮件
func (s *Service) SendEmailVerification(to, code string) error {
return s.SendVerificationCode(to, code, "email_verification")
}
// SendChangeEmail 发送更换邮箱验证码
func (s *Service) SendChangeEmail(to, code string) error {
return s.SendVerificationCode(to, code, "change_email")
}
// send 发送邮件
func (s *Service) send(to []string, subject, body string) error {
e := email.NewEmail()
e.From = fmt.Sprintf("%s <%s>", s.cfg.FromName, s.cfg.Username)
e.To = to
e.Subject = subject
e.HTML = []byte(body)
e.Headers = textproto.MIMEHeader{}
// SMTP认证
auth := smtp.PlainAuth("", s.cfg.Username, s.cfg.Password, s.cfg.SMTPHost)
// 发送邮件
addr := fmt.Sprintf("%s:%d", s.cfg.SMTPHost, s.cfg.SMTPPort)
// 判断端口决定发送方式
// 465端口使用SSL/TLS隐式TLS
// 587端口使用STARTTLS显式TLS
var err error
if s.cfg.SMTPPort == 465 {
// 使用SSL/TLS连接适用于465端口
tlsConfig := &tls.Config{
ServerName: s.cfg.SMTPHost,
InsecureSkipVerify: false, // 生产环境建议设置为false
}
err = e.SendWithTLS(addr, auth, tlsConfig)
} else {
// 使用STARTTLS连接适用于587端口等
err = e.Send(addr, auth)
}
if err != nil {
s.logger.Error("发送邮件失败",
zap.Strings("to", to),
zap.String("subject", subject),
zap.String("smtp_host", s.cfg.SMTPHost),
zap.Int("smtp_port", s.cfg.SMTPPort),
zap.Error(err),
)
return fmt.Errorf("发送邮件失败: %w", err)
}
s.logger.Info("邮件发送成功",
zap.Strings("to", to),
zap.String("subject", subject),
)
return nil
}
// getSubject 获取邮件主题
func (s *Service) getSubject(purpose string) string {
switch purpose {
case "email_verification":
return "【CarrotSkin】邮箱验证"
case "reset_password":
return "【CarrotSkin】重置密码"
case "change_email":
return "【CarrotSkin】更换邮箱验证"
default:
return "【CarrotSkin】验证码"
}
}
// getBody 获取邮件正文
func (s *Service) getBody(code, purpose string) string {
var message string
switch purpose {
case "email_verification":
message = "感谢注册CarrotSkin请使用以下验证码完成邮箱验证"
case "reset_password":
message = "您正在重置密码,请使用以下验证码:"
case "change_email":
message = "您正在更换邮箱,请使用以下验证码验证新邮箱:"
default:
message = "您的验证码为:"
}
return fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>验证码</title>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
<div style="max-width: 600px; margin: 20px auto; background-color: #ffffff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<div style="text-align: center; padding-bottom: 20px;">
<h1 style="color: #ff6b35; margin: 0;">CarrotSkin</h1>
</div>
<div style="padding: 20px 0; border-top: 2px solid #ff6b35; border-bottom: 2px solid #ff6b35;">
<p style="font-size: 16px; color: #333; margin: 0 0 20px 0;">%s</p>
<div style="background-color: #f9f9f9; padding: 20px; text-align: center; border-radius: 4px; margin: 20px 0;">
<span style="font-size: 32px; font-weight: bold; color: #ff6b35; letter-spacing: 5px;">%s</span>
</div>
<p style="font-size: 14px; color: #666; margin: 20px 0 0 0;">验证码有效期为10分钟请及时使用。</p>
<p style="font-size: 14px; color: #666; margin: 10px 0 0 0;">如果这不是您的操作,请忽略此邮件。</p>
</div>
<div style="text-align: center; padding-top: 20px;">
<p style="font-size: 12px; color: #999; margin: 0;">© 2025 CarrotSkin. All rights reserved.</p>
</div>
</div>
</body>
</html>
`, message, code)
}