This commit is contained in:
2024-02-25 08:30:34 +08:00
commit 4947f39e74
273 changed files with 45396 additions and 0 deletions

52
pkg/email/init.go Normal file
View File

@ -0,0 +1,52 @@
package email
import (
"sync"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// Client 默认的邮件发送客户端
var Client Driver
// Lock 读写锁
var Lock sync.RWMutex
// Init 初始化
func Init() {
util.Log().Debug("Initializing email sending queue...")
Lock.Lock()
defer Lock.Unlock()
if Client != nil {
Client.Close()
}
// 读取SMTP设置
options := model.GetSettingByNames(
"fromName",
"fromAdress",
"smtpHost",
"replyTo",
"smtpUser",
"smtpPass",
"smtpEncryption",
)
port := model.GetIntSetting("smtpPort", 25)
keepAlive := model.GetIntSetting("mail_keepalive", 30)
client := NewSMTPClient(SMTPConfig{
Name: options["fromName"],
Address: options["fromAdress"],
ReplyTo: options["replyTo"],
Host: options["smtpHost"],
Port: port,
User: options["smtpUser"],
Password: options["smtpPass"],
Keepalive: keepAlive,
Encryption: model.IsTrueVal(options["smtpEncryption"]),
})
Client = client
}

38
pkg/email/mail.go Normal file
View File

@ -0,0 +1,38 @@
package email
import (
"errors"
"strings"
)
// Driver 邮件发送驱动
type Driver interface {
// Close 关闭驱动
Close()
// Send 发送邮件
Send(to, title, body string) error
}
var (
// ErrChanNotOpen 邮件队列未开启
ErrChanNotOpen = errors.New("email queue is not started")
// ErrNoActiveDriver 无可用邮件发送服务
ErrNoActiveDriver = errors.New("no avaliable email provider")
)
// Send 发送邮件
func Send(to, title, body string) error {
// 忽略通过QQ登录的邮箱
if strings.HasSuffix(to, "@login.qq.com") {
return nil
}
Lock.RLock()
defer Lock.RUnlock()
if Client == nil {
return ErrNoActiveDriver
}
return Client.Send(to, title, body)
}

122
pkg/email/smtp.go Normal file
View File

@ -0,0 +1,122 @@
package email
import (
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/go-mail/mail"
"github.com/google/uuid"
)
// SMTP SMTP协议发送邮件
type SMTP struct {
Config SMTPConfig
ch chan *mail.Message
chOpen bool
}
// SMTPConfig SMTP发送配置
type SMTPConfig struct {
Name string // 发送者名
Address string // 发送者地址
ReplyTo string // 回复地址
Host string // 服务器主机名
Port int // 服务器端口
User string // 用户名
Password string // 密码
Encryption bool // 是否启用加密
Keepalive int // SMTP 连接保留时长
}
// NewSMTPClient 新建SMTP发送队列
func NewSMTPClient(config SMTPConfig) *SMTP {
client := &SMTP{
Config: config,
ch: make(chan *mail.Message, 30),
chOpen: false,
}
client.Init()
return client
}
// Send 发送邮件
func (client *SMTP) Send(to, title, body string) error {
if !client.chOpen {
return ErrChanNotOpen
}
m := mail.NewMessage()
m.SetAddressHeader("From", client.Config.Address, client.Config.Name)
m.SetAddressHeader("Reply-To", client.Config.ReplyTo, client.Config.Name)
m.SetHeader("To", to)
m.SetHeader("Subject", title)
m.SetHeader("Message-ID", util.StrConcat(`"<`, uuid.NewString(), `@`, `cloudreveplus`, `>"`))
m.SetBody("text/html", body)
client.ch <- m
return nil
}
// Close 关闭发送队列
func (client *SMTP) Close() {
if client.ch != nil {
close(client.ch)
}
}
// Init 初始化发送队列
func (client *SMTP) Init() {
go func() {
defer func() {
if err := recover(); err != nil {
client.chOpen = false
util.Log().Error("Exception while sending email: %s, queue will be reset in 10 seconds.", err)
time.Sleep(time.Duration(10) * time.Second)
client.Init()
}
}()
d := mail.NewDialer(client.Config.Host, client.Config.Port, client.Config.User, client.Config.Password)
d.Timeout = time.Duration(client.Config.Keepalive+5) * time.Second
client.chOpen = true
// 是否启用 SSL
d.SSL = false
if client.Config.Encryption {
d.SSL = true
}
d.StartTLSPolicy = mail.OpportunisticStartTLS
var s mail.SendCloser
var err error
open := false
for {
select {
case m, ok := <-client.ch:
if !ok {
util.Log().Debug("Email queue closing...")
client.chOpen = false
return
}
if !open {
if s, err = d.Dial(); err != nil {
panic(err)
}
open = true
}
if err := mail.Send(s, m); err != nil {
util.Log().Warning("Failed to send email: %s", err)
} else {
util.Log().Debug("Email sent.")
}
// 长时间没有新邮件则关闭SMTP连接
case <-time.After(time.Duration(client.Config.Keepalive) * time.Second):
if open {
if err := s.Close(); err != nil {
util.Log().Warning("Failed to close SMTP connection: %s", err)
}
open = false
}
}
}
}()
}

50
pkg/email/template.go Normal file
View File

@ -0,0 +1,50 @@
package email
import (
"fmt"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
)
// NewOveruseNotification 新建超额提醒邮件
func NewOveruseNotification(userName, reason string) (string, string) {
options := model.GetSettingByNames("siteName", "siteURL", "siteTitle", "over_used_template")
replace := map[string]string{
"{siteTitle}": options["siteName"],
"{userName}": userName,
"{notifyReason}": reason,
"{siteUrl}": options["siteURL"],
"{siteSecTitle}": options["siteTitle"],
}
return fmt.Sprintf("【%s】空间容量超额提醒", options["siteName"]),
util.Replace(replace, options["over_used_template"])
}
// NewActivationEmail 新建激活邮件
func NewActivationEmail(userName, activateURL string) (string, string) {
options := model.GetSettingByNames("siteName", "siteURL", "siteTitle", "mail_activation_template")
replace := map[string]string{
"{siteTitle}": options["siteName"],
"{userName}": userName,
"{activationUrl}": activateURL,
"{siteUrl}": options["siteURL"],
"{siteSecTitle}": options["siteTitle"],
}
return fmt.Sprintf("【%s】注册激活", options["siteName"]),
util.Replace(replace, options["mail_activation_template"])
}
// NewResetEmail 新建重设密码邮件
func NewResetEmail(userName, resetURL string) (string, string) {
options := model.GetSettingByNames("siteName", "siteURL", "siteTitle", "mail_reset_pwd_template")
replace := map[string]string{
"{siteTitle}": options["siteName"],
"{userName}": userName,
"{resetUrl}": resetURL,
"{siteUrl}": options["siteURL"],
"{siteSecTitle}": options["siteTitle"],
}
return fmt.Sprintf("【%s】密码重置", options["siteName"]),
util.Replace(replace, options["mail_reset_pwd_template"])
}