Feat: cron / Fix: users status check
This commit is contained in:
parent
44d6ca487c
commit
faf46745bc
22 changed files with 503 additions and 28 deletions
2
go.mod
2
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
|
||||
|
|
5
go.sum
5
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=
|
||||
|
|
4
main.go
4
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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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: `<script type="text/javascript"></script>`, 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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
14
pkg/cache/memo.go
vendored
14
pkg/cache/memo.go
vendored
|
@ -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{
|
||||
|
|
54
pkg/crontab/collect.go
Normal file
54
pkg/crontab/collect.go
Normal file
|
@ -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()
|
||||
}
|
47
pkg/crontab/init.go
Normal file
47
pkg/crontab/init.go
Normal file
|
@ -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()
|
||||
}
|
84
pkg/crontab/vas.go
Normal file
84
pkg/crontab/vas.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
38
pkg/email/init.go
Normal file
38
pkg/email/init.go
Normal file
|
@ -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
|
||||
}
|
27
pkg/email/mail.go
Normal file
27
pkg/email/mail.go
Normal file
|
@ -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)
|
||||
}
|
111
pkg/email/smtp.go
Normal file
111
pkg/email/smtp.go
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
21
pkg/email/template.go
Normal file
21
pkg/email/template.go
Normal file
|
@ -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"])
|
||||
}
|
|
@ -115,6 +115,7 @@ func HookResetPolicy(ctx context.Context, fs *FileSystem) error {
|
|||
}
|
||||
|
||||
fs.Policy = originFile.GetPolicy()
|
||||
fs.User.Policy = *fs.Policy
|
||||
return fs.DispatchHandler()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue