Smail/backend/session/migration_store.go
smail f68c367042
Some checks are pending
Security Checks / security (push) Waiting to run
实现三种数据库迁移并完善认证与迁移写入保护
2026-09-06 01:31:29 +08:00

104 lines
2.4 KiB
Go

package session
import (
"context"
"database/sql"
"errors"
"reflect"
"time"
"git.pcux.cn/xuans/Smail/services/dbmigration"
"github.com/alexedwards/scs/v2"
log "github.com/sirupsen/logrus"
)
var errMigrationSessionWrite = errors.New("session changes are paused during database migration")
// migrationStore 只跳过冻结期间未改变内容的会话续期。
// 不能新增会话、修改权限、延长有效期或伪称已保存安全状态变更。
type migrationStore struct {
scs.Store
codec scs.Codec
paused func() bool
}
func (s *migrationStore) Commit(token string, data []byte, expiry time.Time) error {
if !s.paused() {
return s.Store.Commit(token, data, expiry)
}
previous, found, err := s.Store.Find(token)
if err != nil {
return err
}
if !found {
return errMigrationSessionWrite
}
beforeDeadline, before, err := s.codec.Decode(previous)
if err != nil {
return err
}
afterDeadline, after, err := s.codec.Decode(data)
if err != nil {
return err
}
if !beforeDeadline.Equal(afterDeadline) || !reflect.DeepEqual(before, after) {
return errMigrationSessionWrite
}
return nil
}
func (s *migrationStore) Delete(token string) error {
if s.paused() {
return errMigrationSessionWrite
}
return s.Store.Delete(token)
}
func (s *migrationStore) All() (map[string][]byte, error) {
store, ok := s.Store.(scs.IterableStore)
if !ok {
return nil, errors.New("session store does not support iteration")
}
return store.All()
}
var stopCleanup context.CancelFunc
// Stop 在内部重启时停止旧会话清理器,防止它继续访问旧库。
func Stop() {
if stopCleanup != nil {
stopCleanup()
stopCleanup = nil
}
}
func startCleanup(database *sql.DB, sqlite bool) {
Stop()
ctx, cancel := context.WithCancel(context.Background())
stopCleanup = cancel
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
err := dbmigration.WhileWritable(func() error {
operation, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
query := "DELETE FROM sessions WHERE expiry < CURRENT_TIMESTAMP"
if sqlite {
query = "DELETE FROM sessions WHERE expiry < julianday('now')"
}
_, err := database.ExecContext(operation, query)
return err
})
if err != nil && ctx.Err() == nil {
log.Warn("清理过期会话失败")
}
}
}
}()
}