This commit is contained in:
dela
2026-01-31 22:53:12 +08:00
commit bc639cf460
30 changed files with 6836 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
package telegram
import (
"context"
"log/slog"
"sync"
"time"
"proxyrotator/internal/config"
"proxyrotator/internal/model"
"proxyrotator/internal/store"
"proxyrotator/internal/tester"
)
// Scheduler 定时测活调度器
type Scheduler struct {
mu sync.Mutex
store store.ProxyStore
notifier *Notifier
tester *tester.HTTPTester
cfg *config.Config
ticker *time.Ticker
stopChan chan struct{}
running bool
}
// NewScheduler 创建调度器
func NewScheduler(store store.ProxyStore, notifier *Notifier, cfg *config.Config) *Scheduler {
return &Scheduler{
store: store,
notifier: notifier,
tester: tester.NewHTTPTester(),
cfg: cfg,
stopChan: make(chan struct{}),
}
}
// Start 启动调度器
func (s *Scheduler) Start() {
s.mu.Lock()
defer s.mu.Unlock()
if s.running {
return
}
interval := time.Duration(s.cfg.TelegramTestIntervalMin) * time.Minute
if interval < 5*time.Minute {
interval = 5 * time.Minute
}
s.ticker = time.NewTicker(interval)
s.stopChan = make(chan struct{})
s.running = true
go s.loop()
slog.Info("telegram scheduler started", "interval", interval)
}
// Stop 停止调度器
func (s *Scheduler) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
if !s.running {
return
}
if s.ticker != nil {
s.ticker.Stop()
}
close(s.stopChan)
s.running = false
slog.Info("telegram scheduler stopped")
}
// loop 调度循环
func (s *Scheduler) loop() {
for {
select {
case <-s.stopChan:
return
case <-s.ticker.C:
if err := s.RunTest(context.Background()); err != nil {
slog.Error("scheduled test failed", "error", err)
}
}
}
}
// RunTest 执行测活(所有分组)
func (s *Scheduler) RunTest(ctx context.Context) error {
return s.RunTestWithGroup(ctx, "")
}
// RunTestWithGroup 执行测活(指定分组)
func (s *Scheduler) RunTestWithGroup(ctx context.Context, group string) error {
slog.Info("running scheduled proxy test", "group", group)
// 获取待测试代理
query := model.ProxyQuery{
Group: group,
StatusIn: []model.ProxyStatus{model.StatusUnknown, model.StatusAlive},
OnlyEnabled: true,
Limit: 1000,
}
proxies, err := s.store.List(ctx, query)
if err != nil {
return err
}
if len(proxies) == 0 {
slog.Info("no proxies to test")
return nil
}
// 构建测试规格
spec := model.TestSpec{
URL: s.cfg.TelegramTestURL,
Method: "GET",
Timeout: time.Duration(s.cfg.TelegramTestTimeoutMs) * time.Millisecond,
}
// 执行测试
results := s.tester.TestBatch(ctx, proxies, spec, 50)
// 统计结果
alive, dead := 0, 0
for _, r := range results {
now := r.CheckedAt
if r.OK {
alive++
status := model.StatusAlive
_ = s.store.UpdateHealth(ctx, r.ProxyID, model.HealthPatch{
Status: &status,
ScoreDelta: 1,
SuccessInc: 1,
LatencyMs: &r.LatencyMs,
CheckedAt: &now,
})
} else {
dead++
status := model.StatusDead
_ = s.store.UpdateHealth(ctx, r.ProxyID, model.HealthPatch{
Status: &status,
ScoreDelta: -3,
FailInc: 1,
CheckedAt: &now,
})
}
}
slog.Info("scheduled test completed", "tested", len(results), "alive", alive, "dead", dead)
// 检查是否需要告警
total := len(results)
if total > 0 {
alivePercent := float64(alive) / float64(total) * 100
if alivePercent < float64(s.cfg.TelegramAlertThreshold) {
s.notifier.SendAlert(ctx, alive, dead, total, alivePercent)
}
}
return nil
}