Files
geekai/api/service/smtp_sms_service.go
T
RockYang d4fd38ab7e feat: 迁移 geekai-plus v4.3.1 到开源版
- 同步 Plus v4.3.1 功能源并移除商业 License 闭环
- 更新开源镜像命名、Docker 部署版本和 geekai 数据库配置
- 补充前端 ESLint 检查配置并修复存量解析与模板问题
- 保留 JWT、管理员权限、API Key 和 OAuth 等正常鉴权机制
2026-09-13 14:37:12 +08:00

152 lines
4.1 KiB
Go

package service
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"bytes"
"crypto/tls"
"fmt"
"geekai/core/types"
"mime"
"net/smtp"
"net/textproto"
)
type SmtpService struct {
config *types.SmtpConfig
}
// NewSmtpService 优先使用数据库中的 SMTP 配置(管理后台保存),否则回退到 config.toml。
func NewSmtpService(appConfig *types.AppConfig, sysConfig *types.SystemConfig) *SmtpService {
s := &SmtpService{}
if sysConfig.SMTP.Host != "" && sysConfig.SMTP.Port > 0 {
c := sysConfig.SMTP
s.config = &c
} else {
s.config = &appConfig.SmtpConfig
}
return s
}
func (s *SmtpService) UpdateConfig(config *types.SmtpConfig) {
s.config = config
}
func (s *SmtpService) smtpConfigured() error {
if s.config == nil || s.config.Host == "" || s.config.Port <= 0 {
return fmt.Errorf("SMTP 未配置或无效:请在管理后台填写邮件服务器地址和端口")
}
return nil
}
func (s *SmtpService) SendVerifyCode(to string, code int) error {
if err := s.smtpConfigured(); err != nil {
return err
}
subject := fmt.Sprintf("%s 注册验证码", s.config.AppName)
body := fmt.Sprintf("【%s】:您的验证码为 %d,请不要告诉他人。如非本人操作,请忽略此邮件。", s.config.AppName, code)
auth := smtp.PlainAuth("", s.config.From, s.config.Password, s.config.Host)
if s.config.UseTls {
return s.sendTLS(auth, to, subject, body)
} else {
return s.send(auth, to, subject, body)
}
}
func (s *SmtpService) send(auth smtp.Auth, to string, subject string, body string) error {
// 对主题进行MIME编码
encodedSubject := mime.QEncoding.Encode("UTF-8", subject)
// 组装邮件
message := bytes.NewBuffer(nil)
message.WriteString(fmt.Sprintf("From: \"%s\" <%s>\r\n", s.config.AppName, s.config.From))
message.WriteString(fmt.Sprintf("To: %s\r\n", to))
message.WriteString(fmt.Sprintf("Subject: %s\r\n", encodedSubject))
message.WriteString("\r\n" + body)
// 发送邮件
err := smtp.SendMail(s.config.Host+":"+fmt.Sprint(s.config.Port), auth, s.config.From, []string{to}, message.Bytes())
if err != nil {
return fmt.Errorf("error sending email: %v", err)
}
return err
}
func (s *SmtpService) sendTLS(auth smtp.Auth, to string, subject string, body string) error {
// TLS配置
tlsConfig := &tls.Config{
ServerName: s.config.Host,
}
// 建立TLS连接
conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", s.config.Host, s.config.Port), tlsConfig)
if err != nil {
return fmt.Errorf("error connecting to SMTP server: %v", err)
}
defer conn.Close()
client, err := smtp.NewClient(conn, s.config.Host)
if err != nil {
return fmt.Errorf("error creating SMTP client: %v", err)
}
defer client.Quit()
// 身份验证
if err = client.Auth(auth); err != nil {
return fmt.Errorf("error authenticating: %v", err)
}
// 设置寄件人
if err = client.Mail(s.config.From); err != nil {
return fmt.Errorf("error setting sender: %v", err)
}
// 设置收件人
if err = client.Rcpt(to); err != nil {
return fmt.Errorf("error setting recipient: %v", err)
}
// 发送邮件内容
wc, err := client.Data()
if err != nil {
return fmt.Errorf("error getting data writer: %v", err)
}
defer wc.Close()
header := make(textproto.MIMEHeader)
header.Set("From", s.config.From)
header.Set("To", to)
header.Set("Subject", subject)
// 将邮件头写入
for key, values := range header {
for _, value := range values {
_, err = fmt.Fprintf(wc, "%s: %s\r\n", key, value)
if err != nil {
return fmt.Errorf("error sending email header: %v", err)
}
}
}
_, _ = fmt.Fprintln(wc)
// 将邮件内容写入
_, err = fmt.Fprint(wc, body)
if err != nil {
return fmt.Errorf("error sending email: %v", err)
}
// 发送完毕
err = wc.Close()
if err != nil {
return fmt.Errorf("error closing data writer: %v", err)
}
return nil
}