Smail/backend/services/verification/event_cleanup.go
smail f60d3b0880
Some checks are pending
Security Checks / security (push) Waiting to run
整理前后端与测试构建目录并完善配置和安全引导
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
2026-09-05 19:19:52 +08:00

109 lines
2.3 KiB
Go

package verification
import (
"context"
"sync"
"time"
)
const DefaultOperationalCleanupInterval = time.Hour
type OperationalCleanupRunner struct {
mu sync.Mutex
store *EventStore
rateLimits *PersistentRateLimiter
interval time.Duration
running bool
stopping bool
stop chan struct{}
done chan struct{}
now func() time.Time
}
func NewOperationalCleanupRunner(store *EventStore, rateLimits ...*PersistentRateLimiter) *OperationalCleanupRunner {
runner := &OperationalCleanupRunner{store: store, interval: DefaultOperationalCleanupInterval, now: time.Now}
if len(rateLimits) > 0 {
runner.rateLimits = rateLimits[0]
}
return runner
}
func (r *OperationalCleanupRunner) Start() {
if r == nil || r.store == nil {
return
}
r.mu.Lock()
if r.running || r.stopping {
r.mu.Unlock()
return
}
r.running = true
r.stop = make(chan struct{})
r.done = make(chan struct{})
stop, done := r.stop, r.done
interval := r.interval
r.mu.Unlock()
go func() {
defer close(done)
r.runOnce()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
r.runOnce()
case <-stop:
return
}
}
}()
}
func (r *OperationalCleanupRunner) Stop() {
if r == nil {
return
}
r.mu.Lock()
stop, done := r.stop, r.done
if stop == nil {
r.mu.Unlock()
return
}
r.stop, r.done, r.running = nil, nil, false
r.stopping = true
r.mu.Unlock()
close(stop)
<-done
r.mu.Lock()
r.stop, r.done, r.stopping = nil, nil, false
r.mu.Unlock()
}
func (r *OperationalCleanupRunner) runOnce() {
if r == nil || r.store == nil {
return
}
now := time.Now()
if r.now != nil {
now = r.now()
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
lease, acquired, err := r.store.TryAcquireCleanupLease(ctx, now)
if err != nil || !acquired {
return
}
if _, err = r.store.Cleanup(ctx, now); err == nil && r.rateLimits != nil {
_, err = r.rateLimits.Cleanup(ctx, now)
}
if err != nil {
_ = r.store.FailCleanup(ctx, lease, now)
_ = r.store.Record(EventInput{Purpose: PurposeLogin, Action: "cleanup", Outcome: "failure"})
return
}
_ = r.store.FinishCleanup(ctx, lease, now)
_ = r.store.Record(EventInput{Purpose: PurposeLogin, Action: "cleanup", Outcome: "success"})
}