mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
ad9b336fb9
- Replace tunnel_quota table with user_quota table - Add user-level daily/monthly quota tracking and enforcement - Update user CRUD to include quota configuration - Migrate backup/restore to use user quota fields - Update frontend API and UI for user quota management
217 lines
4.4 KiB
Go
217 lines
4.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
func (h *Handler) StartBackgroundJobs() {
|
|
if h == nil || h.repo == nil {
|
|
return
|
|
}
|
|
|
|
h.jobsMu.Lock()
|
|
if h.jobsStarted {
|
|
h.jobsMu.Unlock()
|
|
return
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
h.jobsCancel = cancel
|
|
h.jobsStarted = true
|
|
h.jobsWG.Add(3)
|
|
h.jobsMu.Unlock()
|
|
|
|
go h.runHourlyStatsLoop(ctx)
|
|
go h.runDailyMaintenanceLoop(ctx)
|
|
go h.runNodeRenewalCycleLoop(ctx)
|
|
}
|
|
|
|
func (h *Handler) StopBackgroundJobs() {
|
|
if h == nil {
|
|
return
|
|
}
|
|
|
|
h.jobsMu.Lock()
|
|
if !h.jobsStarted {
|
|
h.jobsMu.Unlock()
|
|
return
|
|
}
|
|
cancel := h.jobsCancel
|
|
h.jobsCancel = nil
|
|
h.jobsStarted = false
|
|
h.jobsMu.Unlock()
|
|
|
|
if cancel != nil {
|
|
cancel()
|
|
}
|
|
h.jobsWG.Wait()
|
|
}
|
|
|
|
func (h *Handler) runHourlyStatsLoop(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
|
|
for {
|
|
wait := durationUntilNextHour(time.Now())
|
|
timer := time.NewTimer(wait)
|
|
select {
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return
|
|
case <-timer.C:
|
|
h.runStatisticsFlowJob(time.Now())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Handler) runDailyMaintenanceLoop(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
|
|
for {
|
|
wait := durationUntilNextDailyMaintenance(time.Now())
|
|
timer := time.NewTimer(wait)
|
|
select {
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return
|
|
case <-timer.C:
|
|
h.runResetAndExpiryJob(time.Now())
|
|
}
|
|
}
|
|
}
|
|
|
|
func durationUntilNextHour(now time.Time) time.Duration {
|
|
next := now.Truncate(time.Hour).Add(time.Hour)
|
|
return next.Sub(now)
|
|
}
|
|
|
|
func durationUntilNextDailyMaintenance(now time.Time) time.Duration {
|
|
next := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 5, 0, now.Location())
|
|
if !next.After(now) {
|
|
next = next.Add(24 * time.Hour)
|
|
}
|
|
return next.Sub(now)
|
|
}
|
|
|
|
func (h *Handler) runStatisticsFlowJob(now time.Time) {
|
|
if h == nil || h.repo == nil {
|
|
return
|
|
}
|
|
|
|
nowMs := now.UnixMilli()
|
|
cutoffMs := nowMs - int64((48*time.Hour)/time.Millisecond)
|
|
_ = h.repo.PurgeOldStatisticsFlows(cutoffMs)
|
|
|
|
hourMark := now.Truncate(time.Hour)
|
|
hourText := hourMark.Format("15:04")
|
|
createdTime := hourMark.UnixMilli()
|
|
|
|
users, err := h.repo.ListAllUserFlowSnapshots()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, user := range users {
|
|
currentTotal := user.InFlow + user.OutFlow
|
|
increment := currentTotal
|
|
|
|
lastTotal, err := h.repo.GetLastStatisticsFlowTotal(user.UserID)
|
|
if err == nil && lastTotal.Valid {
|
|
increment = currentTotal - lastTotal.Int64
|
|
if increment < 0 {
|
|
increment = currentTotal
|
|
}
|
|
}
|
|
|
|
_ = h.repo.CreateStatisticsFlow(user.UserID, increment, currentTotal, hourText, createdTime)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) runResetAndExpiryJob(now time.Time) {
|
|
if h == nil || h.repo == nil {
|
|
return
|
|
}
|
|
|
|
h.resetMonthlyFlow(now)
|
|
h.resetUserQuotaWindows(now)
|
|
h.disableExpiredUsers(now.UnixMilli())
|
|
h.disableExpiredUserTunnels(now.UnixMilli())
|
|
}
|
|
|
|
func (h *Handler) resetMonthlyFlow(now time.Time) {
|
|
currentDay := now.Day()
|
|
lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).Day()
|
|
|
|
_ = h.repo.ResetUserMonthlyFlow(currentDay, lastDay)
|
|
_ = h.repo.ResetUserTunnelMonthlyFlow(currentDay, lastDay)
|
|
}
|
|
|
|
func (h *Handler) disableExpiredUsers(nowMs int64) {
|
|
userIDs, err := h.repo.ListExpiredActiveUserIDs(nowMs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, userID := range userIDs {
|
|
forwards, err := h.listActiveForwardsByUser(userID)
|
|
if err == nil {
|
|
h.pauseForwardRecords(forwards, nowMs)
|
|
}
|
|
_ = h.repo.DisableUser(userID)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) disableExpiredUserTunnels(nowMs int64) {
|
|
items, err := h.repo.ListExpiredActiveUserTunnels(nowMs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, item := range items {
|
|
forwards, err := h.listActiveForwardsByUserTunnel(item.UserID, item.TunnelID)
|
|
if err == nil {
|
|
h.pauseForwardRecords(forwards, nowMs)
|
|
}
|
|
_ = h.repo.DisableUserTunnel(item.ID)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) runNodeRenewalCycleLoop(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
|
|
for {
|
|
wait := durationUntilNextNodeRenewalCycle(time.Now())
|
|
timer := time.NewTimer(wait)
|
|
select {
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return
|
|
case <-timer.C:
|
|
h.runNodeRenewalCycleJob(time.Now())
|
|
}
|
|
}
|
|
}
|
|
|
|
func durationUntilNextNodeRenewalCycle(now time.Time) time.Duration {
|
|
next := now.Truncate(6 * time.Hour).Add(6 * time.Hour)
|
|
return next.Sub(now)
|
|
}
|
|
|
|
func (h *Handler) runNodeRenewalCycleJob(now time.Time) {
|
|
if h == nil || h.repo == nil {
|
|
return
|
|
}
|
|
|
|
advanced, err := h.repo.AdvanceNodeRenewalCycles(now.UnixMilli())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_ = advanced
|
|
}
|