Files
admin 9b82fbef4e feat: 添加存储配置管理功能
- 添加存储配置管理页面(列表、创建、编辑)
- 支持本地存储、S3、WebDAV、FTP、SFTP 等存储类型
- 添加存储配置测试连接功能
- 本地存储自动初始化且禁止删除
- 修复 Switch 组件状态显示问题
- 添加更新存储配置时的 status 字段支持
2026-05-02 17:48:58 +08:00

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
}