From faf46745bcc1a30f0d5e0464f3bbacdbb4b05123 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Sat, 15 Feb 2020 14:02:21 +0800 Subject: [PATCH] Feat: cron / Fix: users status check --- go.mod | 2 + go.sum | 5 ++ main.go | 4 ++ middleware/auth.go | 4 +- models/migration.go | 7 +-- models/storage_pack.go | 12 +++++ models/user.go | 86 ++++++++++++++++++++++++------ pkg/cache/memo.go | 14 +++++ pkg/crontab/collect.go | 54 +++++++++++++++++++ pkg/crontab/init.go | 47 +++++++++++++++++ pkg/crontab/vas.go | 84 +++++++++++++++++++++++++++++ pkg/email/init.go | 38 ++++++++++++++ pkg/email/mail.go | 27 ++++++++++ pkg/email/smtp.go | 111 +++++++++++++++++++++++++++++++++++++++ pkg/email/template.go | 21 ++++++++ pkg/filesystem/hooks.go | 1 + pkg/task/compress.go | 2 +- pkg/task/decompress.go | 2 +- pkg/task/tranfer.go | 4 +- pkg/webdav/webdav.go | 2 +- service/explorer/file.go | 2 +- service/user/login.go | 2 +- 22 files changed, 503 insertions(+), 28 deletions(-) create mode 100644 pkg/crontab/collect.go create mode 100644 pkg/crontab/init.go create mode 100644 pkg/crontab/vas.go create mode 100644 pkg/email/init.go create mode 100644 pkg/email/mail.go create mode 100644 pkg/email/smtp.go create mode 100644 pkg/email/template.go diff --git a/go.mod b/go.mod index 543af6a..c42dafe 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/gin-contrib/sessions v0.0.1 github.com/gin-gonic/gin v1.4.0 github.com/go-ini/ini v1.50.0 + github.com/go-mail/mail v2.3.1+incompatible github.com/gomodule/redigo v2.0.0+incompatible github.com/google/go-querystring v1.0.0 github.com/gorilla/websocket v1.4.1 @@ -26,6 +27,7 @@ require ( github.com/pkg/errors v0.8.0 github.com/qiniu/api.v7/v7 v7.4.0 github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 + github.com/robfig/cron/v3 v3.0.1 github.com/smartystreets/goconvey v1.6.4 // indirect github.com/speps/go-hashids v2.0.0+incompatible github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index a3dd2c6..0952587 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/go-ini/ini v1.50.0 h1:ogX6RS8VstVN8MJcwhEP78hHhWaI3klN02+97bByabY= github.com/go-ini/ini v1.50.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= +github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -165,6 +167,9 @@ github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTep github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 h1:leEwA4MD1ew0lNgzz6Q4G76G3AEfeci+TMggN6WuFRs= github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/main.go b/main.go index c9491c2..9e96230 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,8 @@ import ( "github.com/HFO4/cloudreve/pkg/authn" "github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/conf" + "github.com/HFO4/cloudreve/pkg/crontab" + "github.com/HFO4/cloudreve/pkg/email" "github.com/HFO4/cloudreve/pkg/task" "github.com/HFO4/cloudreve/routers" "github.com/gin-gonic/gin" @@ -24,6 +26,8 @@ func init() { authn.Init() task.Init() aria2.Init() + email.Init() + crontab.Init() } auth.Init() } diff --git a/middleware/auth.go b/middleware/auth.go index 885d7a4..4226778 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -48,7 +48,7 @@ func CurrentUser() gin.HandlerFunc { session := sessions.Default(c) uid := session.Get("user_id") if uid != nil { - user, err := model.GetUserByID(uid) + user, err := model.GetActiveUserByID(uid) if err == nil { c.Set("user", &user) } @@ -135,7 +135,7 @@ func uploadCallbackCheck(c *gin.Context) (serializer.Response, *model.User) { _ = cache.Deletes([]string{callbackKey}, "callback_") // 查找用户 - user, err := model.GetUserByID(callbackSession.UID) + user, err := model.GetActiveUserByID(callbackSession.UID) if err != nil { return serializer.Err(serializer.CodeCheckLogin, "找不到用户", err), nil } diff --git a/models/migration.go b/models/migration.go index 60c6ffe..59f5844 100644 --- a/models/migration.go +++ b/models/migration.go @@ -89,6 +89,7 @@ func addDefaultSettings() { {Name: "siteDes", Value: `Cloudreve`, Type: "basic"}, {Name: "siteTitle", Value: `平步云端`, Type: "basic"}, {Name: "fromName", Value: `Cloudreve`, Type: "mail"}, + {Name: "mail_keepalive", Value: `30`, Type: "mail"}, {Name: "fromAdress", Value: `no-reply@acg.blue`, Type: "mail"}, {Name: "smtpHost", Value: `smtp.mxhichina.com`, Type: "mail"}, {Name: "smtpPort", Value: `25`, Type: "mail"}, @@ -151,9 +152,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti {Name: "admin_color_body", Value: `fixed-nav sticky-footer bg-light`, Type: "admin"}, {Name: "admin_color_nav", Value: `navbar navbar-expand-lg fixed-top navbar-light bg-light`, Type: "admin"}, {Name: "js_code", Value: ``, Type: "basic"}, - {Name: "sendfile", Value: `0`, Type: "download"}, {Name: "defaultTheme", Value: `#3f51b5`, Type: "basic"}, - {Name: "header", Value: `X-Sendfile`, Type: "download"}, {Name: "themes", Value: `{"#3f51b5":{"palette":{"primary":{"light":"#7986cb","main":"#3f51b5","dark":"#303f9f","contrastText":"#fff"},"secondary":{"light":"#ff4081","main":"#f50057","dark":"#c51162","contrastText":"#fff"},"error":{"light":"#e57373","main":"#f44336","dark":"#d32f2f","contrastText":"#fff"},"explorer":{"filename":"#474849","icon":"#8f8f8f","bgSelected":"#D5DAF0","emptyIcon":"#e8e8e8"}}}}`, Type: "basic"}, {Name: "refererCheck", Value: `true`, Type: "share"}, {Name: "header", Value: `X-Sendfile`, Type: "download"}, @@ -170,6 +169,9 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti {Name: "share_score_rate", Value: "80", Type: "score"}, {Name: "home_view_method", Value: "icon", Type: "view"}, {Name: "share_view_method", Value: "list", Type: "view"}, + {Name: "cron_garbage_collect", Value: "@hourly", Type: "cron"}, + {Name: "cron_notify_user", Value: "@hourly", Type: "cron"}, + {Name: "cron_ban_user", Value: "@hourly", Type: "cron"}, } for _, value := range defaultSettings { @@ -241,7 +243,6 @@ func addDefaultUser() { defaultUser.Nick = "admin" defaultUser.Status = Active defaultUser.GroupID = 1 - defaultUser.PrimaryGroup = 1 err := defaultUser.SetPassword("admin") if err != nil { util.Log().Panic("无法创建密码, %s", err) diff --git a/models/storage_pack.go b/models/storage_pack.go index 89a1930..fff09bd 100644 --- a/models/storage_pack.go +++ b/models/storage_pack.go @@ -59,3 +59,15 @@ func (user *User) GetAvailablePackSize() uint64 { return total } + +// GetExpiredStoragePack 获取已过期的容量包 +func GetExpiredStoragePack() []StoragePack { + var packs []StoragePack + DB.Where("expired_time < ?", time.Now()).Find(&packs) + return packs +} + +// Delete 删除容量包 +func (pack *StoragePack) Delete() error { + return DB.Delete(&pack).Error +} diff --git a/models/user.go b/models/user.go index 28e3036..b21b819 100644 --- a/models/user.go +++ b/models/user.go @@ -26,22 +26,23 @@ const ( type User struct { // 表字段 gorm.Model - Email string `gorm:"type:varchar(100);unique_index"` - Nick string `gorm:"size:50"` - Password string `json:"-"` - Status int - GroupID uint - PrimaryGroup int - ActivationKey string `json:"-"` - Storage uint64 - LastNotify *time.Time - OpenID string `json:"-"` - TwoFactor string `json:"-"` - Delay int - Avatar string - Options string `json:"-",gorm:"type:text"` - Authn string `gorm:"type:text"` - Score int + Email string `gorm:"type:varchar(100);unique_index"` + Nick string `gorm:"size:50"` + Password string `json:"-"` + Status int + GroupID uint + ActivationKey string `json:"-"` + Storage uint64 + OpenID string `json:"-"` + TwoFactor string `json:"-"` + Delay int + Avatar string + Options string `json:"-",gorm:"type:text"` + Authn string `gorm:"type:text"` + Score int + PreviousGroupID uint // 初始用户组 + GroupExpires *time.Time // 用户组过期日期 + NotifyDate *time.Time // 通知超出配额时的日期 // 关联模型 Group Group `gorm:"association_autoupdate:false"` @@ -165,6 +166,13 @@ func GetUserByID(ID interface{}) (User, error) { return user, result.Error } +// GetActiveUserByID 用ID获取可登录用户 +func GetActiveUserByID(ID interface{}) (User, error) { + var user User + result := DB.Set("gorm:auto_preload", true).Where("status = ?", Active).First(&user, ID) + return user, result.Error +} + // GetUserByEmail 用Email获取用户 func GetUserByEmail(email string) (User, error) { var user User @@ -271,3 +279,49 @@ func NewAnonymousUser() *User { func (user *User) IsAnonymous() bool { return user.ID == 0 } + +// Notified 更新用户容量超额通知日期 +func (user *User) Notified() { + if user.NotifyDate == nil { + timeNow := time.Now() + user.NotifyDate = &timeNow + DB.Model(&user).Update("notify_date", user.NotifyDate) + } +} + +// ClearNotified 清除用户通知标记 +func (user *User) ClearNotified() { + DB.Model(&user).Update("notify_date", nil) +} + +// SetStatus 设定用户状态 +func (user *User) SetStatus(status int) { + DB.Model(&user).Update("status", status) +} + +// GetGroupExpiredUsers 获取用户组过期的用户 +func GetGroupExpiredUsers() []User { + var users []User + DB.Where("group_expires < ? and previous_group_id <> 0", time.Now()).Find(&users) + return users +} + +// GetTolerantExpiredUser 获取超过宽容期的用户 +func GetTolerantExpiredUser() []User { + var users []User + DB.Set("gorm:auto_preload", true).Where("notify_date < ?", time.Now().Add( + time.Duration(-GetIntSetting("ban_time", 10))*time.Second), + ).Find(&users) + return users +} + +// GroupFallback 回退到初始用户组 +func (user *User) GroupFallback() { + if user.GroupExpires != nil && user.PreviousGroupID != 0 { + DB.Model(&user).Updates(map[string]interface{}{ + "group_expires": nil, + "previous_group_id": 0, + "group_id": user.PreviousGroupID, + }) + } +} diff --git a/pkg/cache/memo.go b/pkg/cache/memo.go index c1ca7ed..3b5c366 100644 --- a/pkg/cache/memo.go +++ b/pkg/cache/memo.go @@ -1,6 +1,7 @@ package cache import ( + "github.com/HFO4/cloudreve/pkg/util" "sync" "time" ) @@ -46,6 +47,19 @@ func getValue(item interface{}, ok bool) (interface{}, bool) { } +// GarbageCollect 回收已过期的缓存 +func (store *MemoStore) GarbageCollect() { + store.Store.Range(func(key, value interface{}) bool { + if item, ok := value.(itemWithTTL); ok { + if item.expires > 0 && item.expires < time.Now().Unix() { + util.Log().Debug("回收垃圾[%s]", key.(string)) + store.Store.Delete(key) + } + } + return true + }) +} + // NewMemoStore 新建内存存储 func NewMemoStore() *MemoStore { return &MemoStore{ diff --git a/pkg/crontab/collect.go b/pkg/crontab/collect.go new file mode 100644 index 0000000..b247f9b --- /dev/null +++ b/pkg/crontab/collect.go @@ -0,0 +1,54 @@ +package crontab + +import ( + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/cache" + "github.com/HFO4/cloudreve/pkg/util" + "os" + "path/filepath" + "strings" + "time" +) + +func garbageCollect() { + // 清理打包下载产生的临时文件 + collectArchiveFile() + + // 清理过期的内置内存缓存 + if store, ok := cache.Store.(*cache.MemoStore); ok { + collectCache(store) + } + + util.Log().Info("定时任务 [cron_garbage_collect] 执行完毕") +} + +func collectArchiveFile() { + // 读取有效期、目录设置 + tempPath := model.GetSettingByName("temp_path") + expires := model.GetIntSetting("download_timeout", 30) + + // 列出文件 + root := filepath.Join(tempPath, "archive") + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if !info.IsDir() && + strings.HasPrefix(filepath.Base(path), "archive_") && + time.Now().Sub(info.ModTime()).Seconds() > float64(expires) { + util.Log().Debug("删除过期打包下载临时文件 [%s]", path) + // 删除符合条件的文件 + if err := os.Remove(path); err != nil { + util.Log().Debug("临时文件 [%s] 删除失败 , %s", path, err) + } + } + return nil + }) + + if err != nil { + util.Log().Debug("[定时任务] 无法列取临时打包目录") + } + +} + +func collectCache(store *cache.MemoStore) { + util.Log().Debug("清理内存缓存") + store.GarbageCollect() +} diff --git a/pkg/crontab/init.go b/pkg/crontab/init.go new file mode 100644 index 0000000..1ead28f --- /dev/null +++ b/pkg/crontab/init.go @@ -0,0 +1,47 @@ +package crontab + +import ( + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/util" + "github.com/robfig/cron/v3" +) + +// Cron 定时任务 +var Cron *cron.Cron + +// Reload 重新启动定时任务 +func Reload() { + if Cron != nil { + Cron.Stop() + } + Init() +} + +// Init 初始化定时任务 +func Init() { + util.Log().Info("初始化定时任务...") + // 读取cron日程设置 + options := model.GetSettingByNames("cron_garbage_collect", "cron_notify_user", "cron_ban_user") + Cron := cron.New() + for k, v := range options { + var handler func() + switch k { + case "cron_garbage_collect": + handler = garbageCollect + case "cron_notify_user": + handler = notifyExpiredVAS + case "cron_ban_user": + handler = banOverusedUser + default: + util.Log().Warning("未知定时任务类型 [%s],跳过", k) + continue + } + + if _, err := Cron.AddFunc(v, handler); err != nil { + util.Log().Warning("无法启动定时任务 [%s] , %s", k, err) + } + + } + banOverusedUser() + Cron.Start() +} diff --git a/pkg/crontab/vas.go b/pkg/crontab/vas.go new file mode 100644 index 0000000..cfd1d4c --- /dev/null +++ b/pkg/crontab/vas.go @@ -0,0 +1,84 @@ +package crontab + +import ( + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/email" + "github.com/HFO4/cloudreve/pkg/util" +) + +func notifyExpiredVAS() { + checkStoragePack() + checkUserGroup() + util.Log().Info("定时任务 [cron_notify_user] 执行完毕") +} + +// banOverusedUser 封禁超出宽容期的用户 +func banOverusedUser() { + users := model.GetTolerantExpiredUser() + for _, user := range users { + + // 清除最后通知日期标记 + user.ClearNotified() + + // 检查容量是否超额 + if user.Storage > user.Group.MaxStorage+user.GetAvailablePackSize() { + // 封禁用户 + user.SetStatus(model.OveruseBaned) + } + } +} + +// checkUserGroup 检查已过期用户组 +func checkUserGroup() { + users := model.GetGroupExpiredUsers() + for _, user := range users { + + // 将用户回退到初始用户组 + user.GroupFallback() + + // 重新加载用户 + user, _ = model.GetUserByID(user.ID) + + // 检查容量是否超额 + if user.Storage > user.Group.MaxStorage+user.GetAvailablePackSize() { + // 如果超额,则通知用户 + sendNotification(&user, "用户组过期") + // 更新最后通知日期 + user.Notified() + } + } +} + +// checkStoragePack 检查已过期的容量包 +func checkStoragePack() { + packs := model.GetExpiredStoragePack() + for _, pack := range packs { + + //找到所属用户 + user, err := model.GetUserByID(pack.ID) + if err != nil { + util.Log().Warning("[定时任务] 无法获取用户 [UID=%d] 信息, %s", pack.ID, err) + continue + } + + // 检查容量是否超额 + if user.Storage > user.Group.MaxStorage+user.GetAvailablePackSize() { + // 如果超额,则通知用户 + sendNotification(&user, "容量包过期") + + // 删除过期的容量包 + pack.Delete() + + // 更新最后通知日期 + user.Notified() + + } + } +} + +func sendNotification(user *model.User, reason string) { + title, body := email.NewOveruseNotification(user.Nick, reason) + if err := email.Send(user.Email, title, body); err != nil { + util.Log().Warning("无法发送通知邮件, %s", err) + } +} diff --git a/pkg/email/init.go b/pkg/email/init.go new file mode 100644 index 0000000..7df5cbe --- /dev/null +++ b/pkg/email/init.go @@ -0,0 +1,38 @@ +package email + +import model "github.com/HFO4/cloudreve/models" + +// Client 默认的邮件发送客户端 +var Client Driver + +// Init 初始化 +func Init() { + if Client != nil { + Client.Close() + } + + // 读取SMTP设置 + options := model.GetSettingByNames( + "fromName", + "fromAdress", + "smtpHost", + "replyTo", + "smtpUser", + "smtpPass", + ) + 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, + }) + + Client = client +} diff --git a/pkg/email/mail.go b/pkg/email/mail.go new file mode 100644 index 0000000..d93f692 --- /dev/null +++ b/pkg/email/mail.go @@ -0,0 +1,27 @@ +package email + +import "errors" + +// Driver 邮件发送驱动 +type Driver interface { + // Close 关闭驱动 + Close() + // Send 发送邮件 + Send(to, title, body string) error +} + +var ( + // ErrChanNotOpen 邮件队列未开启 + ErrChanNotOpen = errors.New("邮件队列未开启") + // ErrNoActiveDriver 无可用邮件发送服务 + ErrNoActiveDriver = errors.New("无可用邮件发送服务") +) + +// Send 发送邮件 +func Send(to, title, body string) error { + if Client == nil { + return ErrNoActiveDriver + } + + return Client.Send(to, title, body) +} diff --git a/pkg/email/smtp.go b/pkg/email/smtp.go new file mode 100644 index 0000000..f455ba2 --- /dev/null +++ b/pkg/email/smtp.go @@ -0,0 +1,111 @@ +package email + +import ( + "github.com/HFO4/cloudreve/pkg/util" + "github.com/go-mail/mail" + "time" +) + +// 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 string // 是否启用加密 + 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.SetHeader("From", client.Config.Address) + m.SetHeader("To", to) + m.SetHeader("Subject", title) + 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("邮件发送队列出现异常, %s ,30 秒后重新连接", err) + time.Sleep(time.Duration(30) * 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 + + var s mail.SendCloser + var err error + open := false + for { + select { + case m, ok := <-client.ch: + if !ok { + 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("邮件发送失败, %s", err) + } else { + util.Log().Debug("邮件已发送") + } + // 长时间没有新邮件,则关闭SMTP连接 + case <-time.After(time.Duration(client.Config.Keepalive) * time.Second): + if open { + if err := s.Close(); err != nil { + util.Log().Warning("无法关闭 SMTP 连接 %s", err) + } + open = false + } + } + } + }() +} diff --git a/pkg/email/template.go b/pkg/email/template.go new file mode 100644 index 0000000..809336c --- /dev/null +++ b/pkg/email/template.go @@ -0,0 +1,21 @@ +package email + +import ( + "fmt" + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/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"]) +} diff --git a/pkg/filesystem/hooks.go b/pkg/filesystem/hooks.go index 20d3cd4..6b28d47 100644 --- a/pkg/filesystem/hooks.go +++ b/pkg/filesystem/hooks.go @@ -115,6 +115,7 @@ func HookResetPolicy(ctx context.Context, fs *FileSystem) error { } fs.Policy = originFile.GetPolicy() + fs.User.Policy = *fs.Policy return fs.DispatchHandler() } diff --git a/pkg/task/compress.go b/pkg/task/compress.go index 52e4670..eae782c 100644 --- a/pkg/task/compress.go +++ b/pkg/task/compress.go @@ -137,7 +137,7 @@ func NewCompressTask(user *model.User, dst string, dirs, files []uint) (Job, err // NewCompressTaskFromModel 从数据库记录中恢复压缩任务 func NewCompressTaskFromModel(task *model.Task) (Job, error) { - user, err := model.GetUserByID(task.UserID) + user, err := model.GetActiveUserByID(task.UserID) if err != nil { return nil, err } diff --git a/pkg/task/decompress.go b/pkg/task/decompress.go index 7d766ef..b98357d 100644 --- a/pkg/task/decompress.go +++ b/pkg/task/decompress.go @@ -109,7 +109,7 @@ func NewDecompressTask(user *model.User, src, dst string) (Job, error) { // NewDecompressTaskFromModel 从数据库记录中恢复压缩任务 func NewDecompressTaskFromModel(task *model.Task) (Job, error) { - user, err := model.GetUserByID(task.UserID) + user, err := model.GetActiveUserByID(task.UserID) if err != nil { return nil, err } diff --git a/pkg/task/tranfer.go b/pkg/task/tranfer.go index 46a939e..6182a22 100644 --- a/pkg/task/tranfer.go +++ b/pkg/task/tranfer.go @@ -109,7 +109,7 @@ func (job *TransferTask) Recycle() { // NewTransferTask 新建中转任务 func NewTransferTask(user uint, src []string, dst, parent string) (Job, error) { - creator, err := model.GetUserByID(user) + creator, err := model.GetActiveUserByID(user) if err != nil { return nil, err } @@ -134,7 +134,7 @@ func NewTransferTask(user uint, src []string, dst, parent string) (Job, error) { // NewTransferTaskFromModel 从数据库记录中恢复中转任务 func NewTransferTaskFromModel(task *model.Task) (Job, error) { - user, err := model.GetUserByID(task.UserID) + user, err := model.GetActiveUserByID(task.UserID) if err != nil { return nil, err } diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go index 58099bd..aa6f5cf 100644 --- a/pkg/webdav/webdav.go +++ b/pkg/webdav/webdav.go @@ -344,8 +344,8 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst fs.Use("AfterValidateFailed", filesystem.HookUpdateSourceName) } - fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookResetPolicy) + fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookChangeCapacity) fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent) fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize) diff --git a/service/explorer/file.go b/service/explorer/file.go index 1e1c8fc..4bd9e34 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -338,8 +338,8 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se } // 给文件系统分配钩子 - fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookResetPolicy) + fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookChangeCapacity) fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent) fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize) diff --git a/service/user/login.go b/service/user/login.go index 2769fd0..2aad907 100644 --- a/service/user/login.go +++ b/service/user/login.go @@ -36,7 +36,7 @@ func (service *UserLoginService) Login(c *gin.Context) serializer.Response { if authOK, _ := expectedUser.CheckPassword(service.Password); !authOK { return serializer.Err(401, "用户邮箱或密码错误", nil) } - if expectedUser.Status == model.Baned { + if expectedUser.Status == model.Baned || expectedUser.Status == model.OveruseBaned { return serializer.Err(403, "该账号已被封禁", nil) } if expectedUser.Status == model.NotActivicated {