feat: configurable traffic threshold and Feishu bot alerts

This commit is contained in:
Codex committed 2026-10-23 17:49:19 +08:00
1 parent 3e6e481b79
commit 1273f226fd
16 files changed
+660 -20

No files matched your search

+22 -15
View File
@@ -10,14 +10,12 @@ import (
"strings"
"time"
"vpn-platform/backend/internal/alertnotify"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/sessionmgr"
"vpn-platform/backend/internal/store"
)
// 异常流量阈值:15s 内超过此字节数视为突发(约 50Mbps × 15s ≈ 93MB)
const rateAlertThreshold = 90 * 1024 * 1024
// CheckQuota 检查指定隧道的流量配额,返回超额动作("" 表示未超额或无限额)
func CheckQuota(tunnelID uint, deltaRx, deltaTx int64) string {
if store.DB == nil {
@@ -65,7 +63,7 @@ func ResetIfNeeded(policy *model.TrafficPolicy) {
policy.UsedBytes = 0
policy.LastResetAt = now
store.DB.Model(policy).Updates(map[string]interface{}{
"used_bytes": 0,
"used_bytes": 0,
"last_reset_at": now,
})
}
@@ -74,14 +72,20 @@ func ResetIfNeeded(policy *model.TrafficPolicy) {
// CheckRateAbnormal 检查流量速率是否异常(突发流量告警)
func CheckRateAbnormal(tunnelID uint, deltaRx, deltaTx int64, interval time.Duration) bool {
total := deltaRx + deltaTx
if total <= 0 {
if total <= 0 || interval <= 0 {
return false
}
ratePerSec := float64(total) / interval.Seconds()
// 超过阈值(约 50Mbps)
if total > rateAlertThreshold {
sessionmgr.LogConnection(tunnelID, "*", "rate_alert", "",
fmt.Sprintf("突发流量 %.1f MB(%.0f Mbps),15s 内增量异常", float64(total)/(1024*1024), ratePerSec*8/(1024*1024)))
settings, err := alertnotify.Get()
threshold := alertnotify.DefaultThreshold
if err == nil {
threshold = settings.RateThresholdMbps
}
// Mbps 按十进制 1,000,000 bits/s 计算,使用真实采样间隔。
if ratePerSec*8 > float64(threshold)*1_000_000 {
detail := fmt.Sprintf("突发流量 %.1f MiB(%.0f Mbps),采样间隔 %.0f 秒", float64(total)/(1024*1024), ratePerSec*8/1_000_000, interval.Seconds())
sessionmgr.LogConnection(tunnelID, "*", "rate_alert", "", detail)
alertnotify.Notify(tunnelID, "rate_alert", detail)
return true
}
return false
@@ -91,19 +95,22 @@ func CheckRateAbnormal(tunnelID uint, deltaRx, deltaTx int64, interval time.Dura
func HandleExceed(tunnelID uint, action string, tunnelName string) {
switch action {
case "warn":
sessionmgr.LogConnection(tunnelID, "*", "quota_exceed", "",
fmt.Sprintf("隧道「%s」流量配额已用尽(仅告警)", tunnelName))
detail := fmt.Sprintf("隧道「%s」流量配额已用尽(仅告警)", tunnelName)
sessionmgr.LogConnection(tunnelID, "*", "quota_exceed", "", detail)
alertnotify.Notify(tunnelID, "quota_exceed", detail)
case "block":
sessionmgr.LogConnection(tunnelID, "*", "quota_exceed", "",
fmt.Sprintf("隧道「%s」流量配额已用尽,自动停止", tunnelName))
detail := fmt.Sprintf("隧道「%s」流量配额已用尽,自动停止", tunnelName)
sessionmgr.LogConnection(tunnelID, "*", "quota_exceed", "", detail)
alertnotify.Notify(tunnelID, "quota_exceed", detail)
// 由调用方执行 Stop(避免循环依赖,只返回信号)
case "throttle":
// 降速到 1Mbps
var policy model.TrafficPolicy
if store.DB.Where("tunnel_id = ?", tunnelID).First(&policy).Error == nil {
ApplyRateLimit(tunnelID, 1)
sessionmgr.LogConnection(tunnelID, "*", "quota_exceed", "",
fmt.Sprintf("隧道「%s」流量配额已用尽,限速至 1 Mbps", tunnelName))
detail := fmt.Sprintf("隧道「%s」流量配额已用尽,限速至 1 Mbps", tunnelName)
sessionmgr.LogConnection(tunnelID, "*", "quota_exceed", "", detail)
alertnotify.Notify(tunnelID, "quota_exceed", detail)
}
}
}