9b82fbef4e
- 添加存储配置管理页面(列表、创建、编辑) - 支持本地存储、S3、WebDAV、FTP、SFTP 等存储类型 - 添加存储配置测试连接功能 - 本地存储自动初始化且禁止删除 - 修复 Switch 组件状态显示问题 - 添加更新存储配置时的 status 字段支持
211 lines
5.1 KiB
Go
211 lines
5.1 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"mime"
|
|
"net/smtp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type EmailService struct{}
|
|
|
|
func NewEmailService() *EmailService {
|
|
return &EmailService{}
|
|
}
|
|
|
|
type EmailConfig struct {
|
|
Host string
|
|
Port int
|
|
User string
|
|
Password string
|
|
FromName string
|
|
FromEmail string
|
|
UseSSL bool
|
|
}
|
|
|
|
func (s *EmailService) SendEmail(config EmailConfig, to, subject, body string) error {
|
|
if config.Host == "" || config.User == "" || config.Password == "" {
|
|
return fmt.Errorf("邮件配置不完整")
|
|
}
|
|
|
|
from := config.FromEmail
|
|
if from == "" {
|
|
from = config.User
|
|
}
|
|
|
|
addr := fmt.Sprintf("%s:%d", config.Host, config.Port)
|
|
|
|
msg := s.buildMessage(config.FromName, from, to, subject, body)
|
|
|
|
auth := smtp.PlainAuth("", config.User, config.Password, config.Host)
|
|
|
|
if config.Port == 465 || config.UseSSL {
|
|
return s.sendWithImplicitTLS(addr, auth, from, []string{to}, []byte(msg))
|
|
}
|
|
|
|
return s.sendWithSTARTTLS(addr, auth, from, []string{to}, []byte(msg))
|
|
}
|
|
|
|
func (s *EmailService) sendWithImplicitTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
|
|
host := strings.Split(addr, ":")[0]
|
|
|
|
tlsConfig := &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: host,
|
|
}
|
|
|
|
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("TLS连接失败: %v", err)
|
|
}
|
|
|
|
client, err := smtp.NewClient(conn, host)
|
|
if err != nil {
|
|
return fmt.Errorf("创建SMTP客户端失败: %v", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
if err = client.Auth(auth); err != nil {
|
|
return fmt.Errorf("SMTP认证失败: %v", err)
|
|
}
|
|
|
|
if err = client.Mail(from); err != nil {
|
|
return fmt.Errorf("设置发件人失败: %v", err)
|
|
}
|
|
|
|
for _, addr := range to {
|
|
if err = client.Rcpt(addr); err != nil {
|
|
return fmt.Errorf("设置收件人失败: %v", err)
|
|
}
|
|
}
|
|
|
|
w, err := client.Data()
|
|
if err != nil {
|
|
return fmt.Errorf("准备邮件数据失败: %v", err)
|
|
}
|
|
|
|
_, err = w.Write(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("写入邮件内容失败: %v", err)
|
|
}
|
|
|
|
err = w.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("关闭邮件写入失败: %v", err)
|
|
}
|
|
|
|
return client.Quit()
|
|
}
|
|
|
|
func (s *EmailService) sendWithSTARTTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
|
|
host := strings.Split(addr, ":")[0]
|
|
|
|
client, err := smtp.Dial(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("连接SMTP服务器失败: %v", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
if err = client.Hello("localhost"); err != nil {
|
|
return fmt.Errorf("HELO命令失败: %v", err)
|
|
}
|
|
|
|
tlsConfig := &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
ServerName: host,
|
|
}
|
|
|
|
if ok, _ := client.Extension("STARTTLS"); ok {
|
|
if err = client.StartTLS(tlsConfig); err != nil {
|
|
return fmt.Errorf("STARTTLS失败: %v", err)
|
|
}
|
|
}
|
|
|
|
if err = client.Auth(auth); err != nil {
|
|
return fmt.Errorf("SMTP认证失败: %v", err)
|
|
}
|
|
|
|
if err = client.Mail(from); err != nil {
|
|
return fmt.Errorf("设置发件人失败: %v", err)
|
|
}
|
|
|
|
for _, addr := range to {
|
|
if err = client.Rcpt(addr); err != nil {
|
|
return fmt.Errorf("设置收件人失败: %v", err)
|
|
}
|
|
}
|
|
|
|
w, err := client.Data()
|
|
if err != nil {
|
|
return fmt.Errorf("准备邮件数据失败: %v", err)
|
|
}
|
|
|
|
_, err = w.Write(msg)
|
|
if err != nil {
|
|
return fmt.Errorf("写入邮件内容失败: %v", err)
|
|
}
|
|
|
|
err = w.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("关闭邮件写入失败: %v", err)
|
|
}
|
|
|
|
return client.Quit()
|
|
}
|
|
|
|
func (s *EmailService) buildMessage(fromName, from, to, subject, body string) string {
|
|
msg := ""
|
|
|
|
if fromName != "" {
|
|
encodedName := mime.QEncoding.Encode("UTF-8", fromName)
|
|
msg += fmt.Sprintf("From: %s <%s>\r\n", encodedName, from)
|
|
} else {
|
|
msg += fmt.Sprintf("From: %s\r\n", from)
|
|
}
|
|
|
|
msg += fmt.Sprintf("To: %s\r\n", to)
|
|
msg += fmt.Sprintf("Subject: %s\r\n", mime.QEncoding.Encode("UTF-8", subject))
|
|
msg += "MIME-version: 1.0;\r\nContent-Type: text/html; charset=\"UTF-8\";\r\n\r\n"
|
|
msg += body
|
|
|
|
return msg
|
|
}
|
|
|
|
func (s *EmailService) SendTestEmail(config EmailConfig, to, siteName string) error {
|
|
subject := fmt.Sprintf("测试邮件 - %s", siteName)
|
|
body := fmt.Sprintf(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
</head>
|
|
<body style="font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5;">
|
|
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<h2 style="color: #333; margin-bottom: 20px;">邮件发送测试成功</h2>
|
|
<p style="color: #666; line-height: 1.6;">这是一封测试邮件,如果您收到此邮件,说明您的SMTP配置正确。</p>
|
|
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
|
|
<p style="color: #999; font-size: 12px;">此邮件由 %s 系统自动发送,请勿回复。</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`, siteName)
|
|
return s.SendEmail(config, to, subject, body)
|
|
}
|
|
|
|
func ParseBool(value string) bool {
|
|
return value == "true" || value == "1" || strings.ToLower(value) == "yes"
|
|
}
|
|
|
|
func ParseInt(value string, defaultValue int) int {
|
|
if value == "" {
|
|
return defaultValue
|
|
}
|
|
val, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return val
|
|
}
|