From 58362ad1436d3abdf66b3a3aaad3042514c4ec6c Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 11 Mar 2021 20:05:08 +0800 Subject: [PATCH] add grand.D for random time.Duration;add checking and removing session files for package gsession --- os/gsession/gsession_storage_file.go | 91 ++++++++++++++++++++-------- util/grand/grand.go | 15 +++++ util/grand/grand_z_unit_test.go | 18 ++++++ 3 files changed, 98 insertions(+), 26 deletions(-) diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index f1df7d8db..f67aee337 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -7,11 +7,11 @@ package gsession import ( - "fmt" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/internal/json" + "github.com/gogf/gf/util/grand" "os" "time" @@ -36,10 +36,12 @@ type StorageFile struct { } var ( - DefaultStorageFilePath = gfile.TempDir("gsessions") - DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!") - DefaultStorageFileCryptoEnabled = false - DefaultStorageFileLoopInterval = 10 * time.Second + DefaultStorageFilePath = gfile.TempDir("gsessions") + DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!") + DefaultStorageFileCryptoEnabled = false + DefaultStorageFileLoopInterval = 10 * time.Second + DefaultStorageFileRemoveIntervalMin = time.Hour + DefaultStorageFileRemoveIntervalMax = time.Hour * 24 ) // NewStorageFile creates and returns a file storage object for session. @@ -48,10 +50,10 @@ func NewStorageFile(path ...string) *StorageFile { if len(path) > 0 && path[0] != "" { storagePath, _ = gfile.Search(path[0]) if storagePath == "" { - panic(fmt.Sprintf("'%s' does not exist", path[0])) + panic(gerror.Newf("'%s' does not exist", path[0])) } if !gfile.IsWritable(storagePath) { - panic(fmt.Sprintf("'%s' is not writable", path[0])) + panic(gerror.Newf("'%s' is not writable", path[0])) } } if storagePath != "" { @@ -65,24 +67,61 @@ func NewStorageFile(path ...string) *StorageFile { cryptoEnabled: DefaultStorageFileCryptoEnabled, updatingIdSet: gset.NewStrSet(true), } - // Batch updates the TTL for session ids timely. - gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() { - //intlog.Print("StorageFile.timer start") - var ( - id string - err error - ) - for { - if id = s.updatingIdSet.Pop(); id == "" { - break - } - if err = s.doUpdateTTL(id); err != nil { - intlog.Error(err) + + gtimer.AddSingleton(DefaultStorageFileLoopInterval, s.updateSessionTimely) + gtimer.AddOnce( + grand.D(DefaultStorageFileRemoveIntervalMin, DefaultStorageFileRemoveIntervalMax), + s.checkAndRemoveSessionTimely, + ) + return s +} + +// updateSessionTimely batch updates the TTL for sessions timely. +func (s *StorageFile) updateSessionTimely() { + var ( + id string + err error + ) + // Batch updating sessions. + for { + if id = s.updatingIdSet.Pop(); id == "" { + break + } + if err = s.updateSessionTTl(id); err != nil { + intlog.Error(err) + } + } +} + +// checkAndRemoveSessionTimely checks the session storage directory path and removes the expired session files. +func (s *StorageFile) checkAndRemoveSessionTimely() { + defer gtimer.AddOnce( + grand.D(DefaultStorageFileRemoveIntervalMin, DefaultStorageFileRemoveIntervalMax), + s.checkAndRemoveSessionTimely, + ) + + var ( + timestampMilliFile int64 + timestampMilliNow = gtime.Now().TimestampMilli() + files, _ = gfile.ScanDirFile(s.path, "*") + timestampMilliBytes = make([]byte, 8) + ) + for _, path := range files { + file, err := gfile.OpenWithFlag(path, os.O_RDONLY) + if err != nil { + continue + } + if _, err := file.Read(timestampMilliBytes); err == nil { + timestampMilliFile = gbinary.DecodeToInt64(timestampMilliBytes) + if timestampMilliNow >= timestampMilliFile { + intlog.Printf( + "remove expired session file: %s, result:%v", + path, gfile.Remove(path), + ) } } - //intlog.Print("StorageFile.timer end") - }) - return s + file.Close() + } } // SetCryptoKey sets the crypto key for session storage. @@ -229,9 +268,9 @@ func (s *StorageFile) UpdateTTL(id string, ttl time.Duration) error { return nil } -// doUpdateTTL updates the TTL for session id. -func (s *StorageFile) doUpdateTTL(id string) error { - intlog.Printf("StorageFile.doUpdateTTL: %s", id) +// updateSessionTTL updates the TTL for specified session id. +func (s *StorageFile) updateSessionTTl(id string) error { + intlog.Printf("StorageFile.updateSession: %s", id) path := s.sessionFilePath(id) file, err := gfile.OpenWithFlag(path, os.O_WRONLY) if err != nil { diff --git a/util/grand/grand.go b/util/grand/grand.go index 892e6341d..fcd9e3f4d 100644 --- a/util/grand/grand.go +++ b/util/grand/grand.go @@ -9,6 +9,7 @@ package grand import ( "encoding/binary" + "time" "unsafe" ) @@ -97,6 +98,20 @@ func S(n int, symbols ...bool) string { return *(*string)(unsafe.Pointer(&b)) } +// D returns a random time.Duration between min and max: [min, max]. +func D(min, max time.Duration) time.Duration { + multiple := 1 + if min != 0 { + for min%10 == 0 { + multiple *= 10 + min /= 10 + max /= 10 + } + } + n := N(int(min), int(max)) + return time.Duration(n * multiple) +} + // Str randomly picks and returns count of chars from given string . // It also supports unicode string like Chinese/Russian/Japanese, etc. func Str(s string, n int) string { diff --git a/util/grand/grand_z_unit_test.go b/util/grand/grand_z_unit_test.go index 16953742a..133df3282 100644 --- a/util/grand/grand_z_unit_test.go +++ b/util/grand/grand_z_unit_test.go @@ -11,6 +11,7 @@ package grand_test import ( "github.com/gogf/gf/text/gstr" "testing" + "time" "github.com/gogf/gf/test/gtest" "github.com/gogf/gf/util/grand" @@ -73,6 +74,23 @@ func Test_N(t *testing.T) { }) } +func Test_D(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + for i := 0; i < 100; i++ { + t.Assert(grand.D(time.Second, time.Second), time.Second) + } + for i := 0; i < 100; i++ { + t.Assert(grand.D(0, 0), time.Duration(0)) + } + for i := 0; i < 100; i++ { + t.AssertIN( + grand.D(1*time.Second, 3*time.Second), + []time.Duration{1 * time.Second, 2 * time.Second, 3 * time.Second}, + ) + } + }) +} + func Test_Rand(t *testing.T) { gtest.C(t, func(t *gtest.T) { for i := 0; i < 100; i++ {