mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
298 lines
6.2 KiB
Go
298 lines
6.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"go-backend/internal/license"
|
|
)
|
|
|
|
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(7)
|
|
h.jobsMu.Unlock()
|
|
|
|
go h.runHourlyStatsLoop(ctx)
|
|
go h.runDailyMaintenanceLoop(ctx)
|
|
go h.runNodeRenewalCycleLoop(ctx)
|
|
go h.runMetricsIngestion(ctx)
|
|
go h.runHealthChecks(ctx)
|
|
go h.runTunnelQualityProber(ctx)
|
|
go h.runValidateLicenseJob(ctx)
|
|
}
|
|
|
|
func (h *Handler) runValidateLicenseJob(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
ticker := time.NewTicker(12 * time.Hour)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
h.validateLicenseJob()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Handler) validateLicenseJob() {
|
|
if h == nil || h.repo == nil {
|
|
return
|
|
}
|
|
|
|
accountID := "1bc96cac-09de-4cf4-af34-26afdad63a90"
|
|
|
|
key, _ := h.repo.GetViteConfigValue("license_key")
|
|
isCommercial, _ := h.repo.GetViteConfigValue("is_commercial")
|
|
|
|
if key == "" || isCommercial != "true" {
|
|
return // Nothing to validate
|
|
}
|
|
|
|
fingerprint, _ := h.repo.GetViteConfigValue("machine_fingerprint")
|
|
client := license.NewKeygenClient(accountID, "")
|
|
valResp, err := client.ValidateKeyWithFingerprint(key, fingerprint)
|
|
|
|
if err != nil {
|
|
// Network error or timeout. Grace period by not revoking immediately here.
|
|
return
|
|
}
|
|
|
|
if !valResp.Meta.Valid {
|
|
// License is invalid (e.g., revoked, suspended, expired). Downgrade the system.
|
|
now := time.Now().UnixMilli()
|
|
_ = h.repo.UpsertConfig("is_commercial", "false", now)
|
|
} else {
|
|
now := time.Now().UnixMilli()
|
|
expiry := valResp.Data.Attributes.Expiry
|
|
if expiry == "" {
|
|
expiry = "never"
|
|
}
|
|
_ = h.repo.UpsertConfig("license_expiry", expiry, now)
|
|
}
|
|
}
|
|
|
|
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) runMetricsIngestion(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
if h.metrics != nil {
|
|
h.metrics.Start(ctx)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) runHealthChecks(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
if h.healthCheck != nil {
|
|
h.healthCheck.Start(ctx)
|
|
}
|
|
}
|
|
|
|
func (h *Handler) runTunnelQualityProber(ctx context.Context) {
|
|
defer h.jobsWG.Done()
|
|
if h == nil || h.qualityProber == nil {
|
|
return
|
|
}
|
|
|
|
h.qualityProber.Start(ctx)
|
|
}
|
|
|
|
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
|
|
}
|