periodic_traffic_reset_job.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package job
  2. import (
  3. "time"
  4. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  5. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  6. )
  7. // Period represents the time period for traffic resets.
  8. type Period string
  9. // PeriodicTrafficResetJob resets traffic statistics for inbounds based on their configured reset period.
  10. type PeriodicTrafficResetJob struct {
  11. inboundService service.InboundService
  12. clientService service.ClientService
  13. xrayService service.XrayService
  14. period Period
  15. location *time.Location
  16. }
  17. // NewPeriodicTrafficResetJob creates a new periodic traffic reset job for the specified period.
  18. func NewPeriodicTrafficResetJob(period Period, location *time.Location) *PeriodicTrafficResetJob {
  19. return &PeriodicTrafficResetJob{
  20. period: period,
  21. location: location,
  22. }
  23. }
  24. func monthlyResetDue(resetDay int, now time.Time) bool {
  25. if resetDay < 1 {
  26. resetDay = 1
  27. }
  28. lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).Day()
  29. return now.Day() == min(resetDay, lastDay)
  30. }
  31. // Run resets traffic statistics for all inbounds that match the configured reset
  32. // period, then for the clients carrying that period on their own (#5497).
  33. func (j *PeriodicTrafficResetJob) Run() {
  34. j.resetInboundsOnSchedule()
  35. j.resetClientsOnTheirOwnCycle()
  36. }
  37. func (j *PeriodicTrafficResetJob) resetInboundsOnSchedule() {
  38. inbounds, err := j.inboundService.GetInboundsByTrafficReset(string(j.period))
  39. if err != nil {
  40. logger.Warning("Failed to get inbounds for traffic reset:", err)
  41. return
  42. }
  43. if j.period == "monthly" {
  44. now := time.Now().In(j.location)
  45. due := inbounds[:0]
  46. for _, inbound := range inbounds {
  47. if monthlyResetDue(inbound.TrafficResetDay, now) {
  48. due = append(due, inbound)
  49. }
  50. }
  51. inbounds = due
  52. }
  53. if len(inbounds) == 0 {
  54. return
  55. }
  56. logger.Infof("Running periodic traffic reset job for period: %s (%d matching inbounds)", j.period, len(inbounds))
  57. resetCount := 0
  58. for _, inbound := range inbounds {
  59. resetInboundErr := j.inboundService.ResetInboundTraffic(inbound.Id)
  60. if resetInboundErr != nil {
  61. logger.Warning("Failed to reset traffic for inbound", inbound.Id, ":", resetInboundErr)
  62. }
  63. resetClientErr := j.clientService.ResetAllClientTraffics(&j.inboundService, inbound.Id)
  64. if resetClientErr != nil {
  65. logger.Warning("Failed to reset traffic for all users of inbound", inbound.Id, ":", resetClientErr)
  66. }
  67. if resetInboundErr == nil && resetClientErr == nil {
  68. resetCount++
  69. }
  70. }
  71. if resetCount > 0 {
  72. logger.Infof("Periodic traffic reset completed: %d inbounds reset", resetCount)
  73. }
  74. }
  75. // resetClientsOnTheirOwnCycle resets clients whose cycle is set individually. A
  76. // client inside an inbound on the same cycle is reset twice, which is harmless.
  77. func (j *PeriodicTrafficResetJob) resetClientsOnTheirOwnCycle() {
  78. cycles, err := j.clientService.GetClientsByTrafficReset(string(j.period))
  79. if err != nil {
  80. logger.Warning("Failed to get clients for traffic reset:", err)
  81. return
  82. }
  83. now := time.Now().In(j.location)
  84. due := make([]service.ClientResetCycle, 0, len(cycles))
  85. for _, c := range cycles {
  86. // Monthly clients come due on their own day, the rule the inbound-level
  87. // schedule already follows.
  88. if j.period == "monthly" && !monthlyResetDue(c.TrafficResetDay, now) {
  89. continue
  90. }
  91. // A reset re-enables, which is right for a client the quota switched off
  92. // and wrong for one an operator switched off by hand.
  93. if !c.Enable && !c.Depleted() {
  94. continue
  95. }
  96. due = append(due, c)
  97. }
  98. if len(due) == 0 {
  99. return
  100. }
  101. logger.Infof("Running periodic traffic reset job for period: %s (%d matching clients)", j.period, len(due))
  102. resetCount := 0
  103. needRestart := false
  104. for _, c := range due {
  105. // ResetTrafficByEmail rather than a bulk UPDATE: it is the path that also
  106. // propagates to the client's node and clears the MTProto sidecar quota.
  107. nr, resetErr := j.clientService.ResetTrafficByEmail(&j.inboundService, c.Email)
  108. if resetErr != nil {
  109. logger.Warning("Failed to reset traffic for client", c.Email, ":", resetErr)
  110. continue
  111. }
  112. needRestart = needRestart || nr
  113. resetCount++
  114. }
  115. // Dropping this leaves a re-enabled client absent from the running core until
  116. // something unrelated restarts it.
  117. if needRestart {
  118. j.xrayService.SetToNeedRestart()
  119. }
  120. if resetCount > 0 {
  121. logger.Infof("Periodic traffic reset completed: %d clients reset", resetCount)
  122. }
  123. }