- 将高密度服务与处理器日志迁移到新日志系统(LegacyPrintf/结构化日志) - 增加 stdlog bridge 与兼容测试,保留旧日志捕获能力 - 将 OpenAI 断流告警改为结构化 Warn 并改造对应测试为 sink 捕获 - 补齐后端相关文件 logger 引用并通过全量 go test
121 lines
3.1 KiB
Go
121 lines
3.1 KiB
Go
package service
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
|
"github.com/robfig/cron/v3"
|
|
)
|
|
|
|
var soraCleanupCronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
|
|
|
// SoraMediaCleanupService 定期清理本地媒体文件
|
|
type SoraMediaCleanupService struct {
|
|
storage *SoraMediaStorage
|
|
cfg *config.Config
|
|
|
|
cron *cron.Cron
|
|
|
|
startOnce sync.Once
|
|
stopOnce sync.Once
|
|
}
|
|
|
|
func NewSoraMediaCleanupService(storage *SoraMediaStorage, cfg *config.Config) *SoraMediaCleanupService {
|
|
return &SoraMediaCleanupService{
|
|
storage: storage,
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
func (s *SoraMediaCleanupService) Start() {
|
|
if s == nil || s.cfg == nil {
|
|
return
|
|
}
|
|
if !s.cfg.Sora.Storage.Cleanup.Enabled {
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] not started (disabled)")
|
|
return
|
|
}
|
|
if s.storage == nil || !s.storage.Enabled() {
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] not started (storage disabled)")
|
|
return
|
|
}
|
|
|
|
s.startOnce.Do(func() {
|
|
schedule := strings.TrimSpace(s.cfg.Sora.Storage.Cleanup.Schedule)
|
|
if schedule == "" {
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] not started (empty schedule)")
|
|
return
|
|
}
|
|
loc := time.Local
|
|
if strings.TrimSpace(s.cfg.Timezone) != "" {
|
|
if parsed, err := time.LoadLocation(strings.TrimSpace(s.cfg.Timezone)); err == nil && parsed != nil {
|
|
loc = parsed
|
|
}
|
|
}
|
|
c := cron.New(cron.WithParser(soraCleanupCronParser), cron.WithLocation(loc))
|
|
if _, err := c.AddFunc(schedule, func() { s.runCleanup() }); err != nil {
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] not started (invalid schedule=%q): %v", schedule, err)
|
|
return
|
|
}
|
|
s.cron = c
|
|
s.cron.Start()
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] started (schedule=%q tz=%s)", schedule, loc.String())
|
|
})
|
|
}
|
|
|
|
func (s *SoraMediaCleanupService) Stop() {
|
|
if s == nil {
|
|
return
|
|
}
|
|
s.stopOnce.Do(func() {
|
|
if s.cron != nil {
|
|
ctx := s.cron.Stop()
|
|
select {
|
|
case <-ctx.Done():
|
|
case <-time.After(3 * time.Second):
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] cron stop timed out")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *SoraMediaCleanupService) runCleanup() {
|
|
if s.cfg == nil || s.storage == nil {
|
|
return
|
|
}
|
|
retention := s.cfg.Sora.Storage.Cleanup.RetentionDays
|
|
if retention <= 0 {
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] skipped (retention_days=%d)", retention)
|
|
return
|
|
}
|
|
cutoff := time.Now().AddDate(0, 0, -retention)
|
|
deleted := 0
|
|
|
|
roots := []string{s.storage.ImageRoot(), s.storage.VideoRoot()}
|
|
for _, root := range roots {
|
|
if root == "" {
|
|
continue
|
|
}
|
|
_ = filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
if info.ModTime().Before(cutoff) {
|
|
if rmErr := os.Remove(p); rmErr == nil {
|
|
deleted++
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
logger.LegacyPrintf("service.sora_media_cleanup", "[SoraCleanup] cleanup finished, deleted=%d", deleted)
|
|
}
|