idempotency_cleanup_service.go 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. package service
  2. import (
  3. "context"
  4. "sync"
  5. "time"
  6. "github.com/Wei-Shaw/sub2api/internal/config"
  7. "github.com/Wei-Shaw/sub2api/internal/pkg/logger"
  8. )
  9. // IdempotencyCleanupService 定期清理已过期的幂等记录,避免表无限增长。
  10. type IdempotencyCleanupService struct {
  11. repo IdempotencyRepository
  12. interval time.Duration
  13. batch int
  14. startOnce sync.Once
  15. stopOnce sync.Once
  16. stopCh chan struct{}
  17. }
  18. func NewIdempotencyCleanupService(repo IdempotencyRepository, cfg *config.Config) *IdempotencyCleanupService {
  19. interval := 60 * time.Second
  20. batch := 500
  21. if cfg != nil {
  22. if cfg.Idempotency.CleanupIntervalSeconds > 0 {
  23. interval = time.Duration(cfg.Idempotency.CleanupIntervalSeconds) * time.Second
  24. }
  25. if cfg.Idempotency.CleanupBatchSize > 0 {
  26. batch = cfg.Idempotency.CleanupBatchSize
  27. }
  28. }
  29. return &IdempotencyCleanupService{
  30. repo: repo,
  31. interval: interval,
  32. batch: batch,
  33. stopCh: make(chan struct{}),
  34. }
  35. }
  36. func (s *IdempotencyCleanupService) Start() {
  37. if s == nil || s.repo == nil {
  38. return
  39. }
  40. s.startOnce.Do(func() {
  41. logger.LegacyPrintf("service.idempotency_cleanup", "[IdempotencyCleanup] started interval=%s batch=%d", s.interval, s.batch)
  42. go s.runLoop()
  43. })
  44. }
  45. func (s *IdempotencyCleanupService) Stop() {
  46. if s == nil {
  47. return
  48. }
  49. s.stopOnce.Do(func() {
  50. close(s.stopCh)
  51. logger.LegacyPrintf("service.idempotency_cleanup", "[IdempotencyCleanup] stopped")
  52. })
  53. }
  54. func (s *IdempotencyCleanupService) runLoop() {
  55. ticker := time.NewTicker(s.interval)
  56. defer ticker.Stop()
  57. // 启动后先清理一轮,防止重启后积压。
  58. s.cleanupOnce()
  59. for {
  60. select {
  61. case <-ticker.C:
  62. s.cleanupOnce()
  63. case <-s.stopCh:
  64. return
  65. }
  66. }
  67. }
  68. func (s *IdempotencyCleanupService) cleanupOnce() {
  69. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  70. defer cancel()
  71. deleted, err := s.repo.DeleteExpired(ctx, time.Now(), s.batch)
  72. if err != nil {
  73. logger.LegacyPrintf("service.idempotency_cleanup", "[IdempotencyCleanup] cleanup failed err=%v", err)
  74. return
  75. }
  76. if deleted > 0 {
  77. logger.LegacyPrintf("service.idempotency_cleanup", "[IdempotencyCleanup] cleaned expired records count=%d", deleted)
  78. }
  79. }