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(` 验证码

CarrotSkin

%s

%s

验证码有效期为10分钟,请及时使用。

如果这不是您的操作,请忽略此邮件。

© 2025 CarrotSkin. All rights reserved.

`, message, code) }