完善流量使用统计

This commit is contained in:
dushixiang committed 2026-01-10 17:59:28 +08:00
1 parent bca13fab7b
commit f4d6a3fc3e
12 files changed
+364 -239

No files matched your search

+1 -1
View File
@@ -445,7 +445,7 @@ func startTrafficResetCheck(ctx context.Context, components *AppComponents, logg
logger.Info("流量重置检查任务已停止")
return
case <-ticker.C:
if err := components.AgentService.CheckAndResetTraffic(ctx); err != nil {
if err := components.TrafficService.CheckAndResetTraffic(ctx); err != nil {
logger.Error("流量重置检查失败", zap.Error(err))
}
}
+5 -3
View File
@@ -268,7 +268,9 @@ func (h *AgentHandler) UpdateTrafficConfig(c echo.Context) error {
var req struct {
Enabled bool `json:"enabled"`
Type string `json:"type"` // 流量类型: recv/send/both
Limit uint64 `json:"limit"`
Used uint64 `json:"used"` // 手动设置已使用流量(可选)
ResetDay int `json:"resetDay"`
}
@@ -277,7 +279,7 @@ func (h *AgentHandler) UpdateTrafficConfig(c echo.Context) error {
}
ctx := c.Request().Context()
if err := h.agentService.UpdateTrafficConfig(ctx, agentID, req.Enabled, req.Limit, req.ResetDay); err != nil {
if err := h.trafficService.UpdateTrafficConfig(ctx, agentID, req.Enabled, req.Type, req.Limit, req.ResetDay, req.Used); err != nil {
return err
}
@@ -289,7 +291,7 @@ func (h *AgentHandler) GetTrafficStats(c echo.Context) error {
agentID := c.Param("id")
ctx := c.Request().Context()
stats, err := h.agentService.GetTrafficStats(ctx, agentID)
stats, err := h.trafficService.GetTrafficStats(ctx, agentID)
if err != nil {
return err
}
@@ -302,7 +304,7 @@ func (h *AgentHandler) ResetAgentTraffic(c echo.Context) error {
agentID := c.Param("id")
ctx := c.Request().Context()
if err := h.agentService.ResetAgentTraffic(ctx, agentID); err != nil {
if err := h.trafficService.ResetAgentTraffic(ctx, agentID); err != nil {
return err
}
+5 -3
View File
@@ -10,6 +10,7 @@ import (
type AgentHandler struct {
logger *zap.Logger
agentService *service.AgentService
trafficService *service.TrafficService
metricService *service.MetricService
monitorSvc *service.MonitorService
tamperService *service.TamperService
@@ -19,13 +20,14 @@ type AgentHandler struct {
upgrader websocket.Upgrader
}
func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, metricService *service.MetricService,
monitorService *service.MonitorService, tamperService *service.TamperService, ddnsService *service.DDNSService,
sshLoginService *service.SSHLoginService, wsManager *ws.Manager) *AgentHandler {
func NewAgentHandler(logger *zap.Logger, agentService *service.AgentService, trafficService *service.TrafficService,
metricService *service.MetricService, monitorService *service.MonitorService, tamperService *service.TamperService,
ddnsService *service.DDNSService, sshLoginService *service.SSHLoginService, wsManager *ws.Manager) *AgentHandler {
h := &AgentHandler{
logger: logger,
agentService: agentService,
trafficService: trafficService,
metricService: metricService,
monitorSvc: monitorService,
tamperService: tamperService,
+2
View File
@@ -34,11 +34,13 @@ type Agent struct {
// TrafficStatsData 流量统计数据
type TrafficStatsData struct {
Enabled bool `json:"enabled"` // 是否启用
Type string `json:"type"` // 统计类型: "recv"进站, "send"出站, "both"全部
Limit uint64 `json:"limit"` // 流量限额(字节), 0表示不限制
Used uint64 `json:"used"` // 当前周期已使用流量(字节)
ResetDay int `json:"resetDay"` // 流量重置日期(1-31), 0表示不自动重置
PeriodStart int64 `json:"periodStart"` // 当前周期开始时间(时间戳毫秒)
BaselineRecv uint64 `json:"baselineRecv"` // 当前周期流量基线(BytesRecvTotal)
BaselineSend uint64 `json:"baselineSend"` // 当前周期流量基线(BytesSentTotal)
AlertSent80 bool `json:"alertSent80"` // 是否已发送80%告警
AlertSent90 bool `json:"alertSent90"` // 是否已发送90%告警
AlertSent100 bool `json:"alertSent100"` // 是否已发送100%告警
-220
View File
@@ -13,7 +13,6 @@ import (
"github.com/dushixiang/pika/internal/repo"
"github.com/go-orz/orz"
"go.uber.org/zap"
"gorm.io/datatypes"
"gorm.io/gorm"
)
@@ -478,222 +477,3 @@ func (s *AgentService) InitStatus(ctx context.Context) error {
}
return nil
}
// UpdateTrafficConfig 更新流量配置
func (s *AgentService) UpdateTrafficConfig(ctx context.Context, agentID string, enabled bool, limit uint64, resetDay int) error {
if resetDay < 0 || resetDay > 31 {
return fmt.Errorf("重置日期必须在0-31之间")
}
agent, err := s.AgentRepo.FindById(ctx, agentID)
if err != nil {
return err
}
now := time.Now().UnixMilli()
stats := agent.TrafficStats.Data()
oldResetDay := stats.ResetDay
oldEnabled := stats.Enabled
// 设置配置
stats.Enabled = enabled
stats.Limit = limit
stats.ResetDay = resetDay
// 如果是首次启用、禁用后重新启用,或修改重置日期,重置流量统计
if (!oldEnabled && stats.Enabled) || (stats.Enabled && resetDay != oldResetDay) || (stats.Enabled && stats.PeriodStart == 0) {
stats.Used = 0
stats.PeriodStart = now
stats.BaselineRecv = 0 // 下次上报时会设置正确的基线
stats.AlertSent80 = false
stats.AlertSent90 = false
stats.AlertSent100 = false
}
// 如果禁用流量统计,清空相关数据
if !stats.Enabled {
stats.Used = 0
stats.PeriodStart = 0
stats.BaselineRecv = 0
stats.AlertSent80 = false
stats.AlertSent90 = false
stats.AlertSent100 = false
}
agent.TrafficStats = datatypes.NewJSONType(stats)
agent.UpdatedAt = now
return s.AgentRepo.UpdateById(ctx, &agent)
}
// GetTrafficStats 获取流量统计信息
func (s *AgentService) GetTrafficStats(ctx context.Context, agentID string) (*TrafficStats, error) {
agent, err := s.AgentRepo.FindById(ctx, agentID)
if err != nil {
return nil, err
}
trafficData := agent.TrafficStats.Data()
stats := &TrafficStats{
Enabled: trafficData.Enabled,
Limit: trafficData.Limit,
Used: trafficData.Used,
ResetDay: trafficData.ResetDay,
PeriodStart: trafficData.PeriodStart,
Alerts: TrafficAlerts{
Sent80: trafficData.AlertSent80,
Sent90: trafficData.AlertSent90,
Sent100: trafficData.AlertSent100,
},
}
// 计算使用百分比
if trafficData.Limit > 0 {
stats.UsedPercent = float64(trafficData.Used) / float64(trafficData.Limit) * 100
if trafficData.Used < trafficData.Limit {
stats.Remaining = trafficData.Limit - trafficData.Used
} else {
stats.Remaining = 0
}
}
// 计算下次重置日期和剩余天数
if trafficData.ResetDay > 0 && trafficData.PeriodStart > 0 {
periodStart := time.UnixMilli(trafficData.PeriodStart)
nextReset := calculateNextResetDate(periodStart, trafficData.ResetDay)
stats.PeriodEnd = nextReset.UnixMilli()
stats.DaysUntilReset = int(time.Until(nextReset).Hours() / 24)
if stats.DaysUntilReset < 0 {
stats.DaysUntilReset = 0
}
}
return stats, nil
}
// ResetAgentTraffic 重置探针流量
func (s *AgentService) ResetAgentTraffic(ctx context.Context, agentID string) error {
agent, err := s.AgentRepo.FindById(ctx, agentID)
if err != nil {
return err
}
now := time.Now().UnixMilli()
stats := agent.TrafficStats.Data()
stats.Used = 0
stats.BaselineRecv = 0 // 下次上报时会设置正确的基线
stats.PeriodStart = now
stats.AlertSent80 = false
stats.AlertSent90 = false
stats.AlertSent100 = false
agent.TrafficStats = datatypes.NewJSONType(stats)
agent.UpdatedAt = now
s.logger.Info("探针流量已重置",
zap.String("agentId", agentID),
zap.String("agentName", agent.Name))
return s.AgentRepo.UpdateById(ctx, &agent)
}
// CheckAndResetTraffic 检查并重置所有到期的探针流量(定时任务调用)
func (s *AgentService) CheckAndResetTraffic(ctx context.Context) error {
agents, err := s.AgentRepo.FindAgentsWithTrafficReset(ctx)
if err != nil {
return err
}
now := time.Now()
resetCount := 0
for _, agent := range agents {
if s.shouldResetTraffic(&agent, now) {
if err := s.ResetAgentTraffic(ctx, agent.ID); err != nil {
s.logger.Error("重置探针流量失败",
zap.String("agentId", agent.ID),
zap.Error(err))
continue
}
resetCount++
}
}
if resetCount > 0 {
s.logger.Info("流量重置检查完成", zap.Int("重置数量", resetCount))
}
return nil
}
// shouldResetTraffic 判断是否需要重置流量
func (s *AgentService) shouldResetTraffic(agent *models.Agent, now time.Time) bool {
stats := agent.TrafficStats.Data()
// 流量统计未启用、未配置重置日期或周期未开始时不重置
if !stats.Enabled || stats.ResetDay == 0 || stats.PeriodStart == 0 {
return false
}
periodStart := time.UnixMilli(stats.PeriodStart)
nextReset := calculateNextResetDate(periodStart, stats.ResetDay)
return now.After(nextReset) || now.Equal(nextReset)
}
// calculateNextResetDate 计算下次重置日期
func calculateNextResetDate(periodStart time.Time, resetDay int) time.Time {
year, month, day := periodStart.Date()
location := periodStart.Location()
// 先计算当月的重置日期
currentMonthLastDay := time.Date(year, month+1, 0, 0, 0, 0, 0, location).Day()
currentMonthResetDay := resetDay
if resetDay > currentMonthLastDay {
currentMonthResetDay = currentMonthLastDay
}
currentMonthReset := time.Date(year, month, currentMonthResetDay, 0, 0, 0, 0, location)
// 如果当月的重置日期还没到(在 periodStart 之后或同一天),返回当月重置日期
if day < currentMonthResetDay || (day == currentMonthResetDay && periodStart.Before(currentMonthReset)) {
return currentMonthReset
}
// 当月重置日期已过,计算下个月的重置日期
nextMonth := month + 1
nextYear := year
if nextMonth > 12 {
nextMonth = 1
nextYear++
}
// 处理月末日期(如2月没有31号)
nextMonthLastDay := time.Date(nextYear, nextMonth+1, 0, 0, 0, 0, 0, location).Day()
nextMonthResetDay := resetDay
if resetDay > nextMonthLastDay {
nextMonthResetDay = nextMonthLastDay
}
return time.Date(nextYear, nextMonth, nextMonthResetDay, 0, 0, 0, 0, location)
}
// TrafficStats 流量统计信息
type TrafficStats struct {
Enabled bool `json:"enabled"`
Limit uint64 `json:"limit"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
Remaining uint64 `json:"remaining"`
ResetDay int `json:"resetDay"`
PeriodStart int64 `json:"periodStart"`
PeriodEnd int64 `json:"periodEnd"`
DaysUntilReset int `json:"daysUntilReset"`
Alerts TrafficAlerts `json:"alerts"`
}
// TrafficAlerts 流量告警状态
type TrafficAlerts struct {
Sent80 bool `json:"sent80"`
Sent90 bool `json:"sent90"`
Sent100 bool `json:"sent100"`
}
+1 -1
View File
@@ -128,7 +128,7 @@ func (s *MetricService) HandleMetricData(ctx context.Context, agentID string, me
TotalInterfaces: len(networkDataList),
}
// 更新流量统计
if err := s.trafficService.UpdateAgentTraffic(ctx, agentID, totalRecvTotal); err != nil {
if err := s.trafficService.UpdateAgentTraffic(ctx, agentID, totalRecvTotal, totalSentTotal); err != nil {
s.logger.Error("更新探针流量统计失败",
zap.String("agentId", agentID),
zap.Error(err))
+285 -8
View File
@@ -27,7 +27,7 @@ func NewTrafficService(logger *zap.Logger, db *gorm.DB) *TrafficService {
}
// UpdateAgentTraffic 更新探针流量统计(每次上报网络指标时调用)
func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, currentRecvTotal uint64) error {
func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string, currentRecvTotal, currentSentTotal uint64) error {
agent, err := s.agentRepo.FindById(ctx, agentID)
if err != nil {
return err
@@ -41,9 +41,15 @@ func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string,
return nil
}
// 设置默认类型
if stats.Type == "" {
stats.Type = "recv" // 默认只统计进站流量,向后兼容
}
// 初始化基线(首次统计)
if stats.BaselineRecv == 0 {
if stats.BaselineRecv == 0 && stats.BaselineSend == 0 {
stats.BaselineRecv = currentRecvTotal
stats.BaselineSend = currentSentTotal
stats.Used = 0
if stats.PeriodStart == 0 {
stats.PeriodStart = time.Now().UnixMilli()
@@ -53,16 +59,38 @@ func (s *TrafficService) UpdateAgentTraffic(ctx context.Context, agentID string,
}
// 检测计数器重置(探针重启)
if currentRecvTotal < stats.BaselineRecv {
recvReset := currentRecvTotal < stats.BaselineRecv
sendReset := currentSentTotal < stats.BaselineSend
if recvReset || sendReset {
s.logger.Warn("检测到流量计数器重置",
zap.String("agentId", agentID),
zap.Uint64("baseline", stats.BaselineRecv),
zap.Uint64("current", currentRecvTotal))
stats.BaselineRecv = currentRecvTotal
zap.Uint64("baselineRecv", stats.BaselineRecv),
zap.Uint64("currentRecv", currentRecvTotal),
zap.Uint64("baselineSend", stats.BaselineSend),
zap.Uint64("currentSend", currentSentTotal))
if recvReset {
stats.BaselineRecv = currentRecvTotal
}
if sendReset {
stats.BaselineSend = currentSentTotal
}
// 保持 Used 不变,避免丢失已统计的流量
} else {
// 计算使用量
stats.Used = currentRecvTotal - stats.BaselineRecv
// 根据配置的类型计算使用量
switch stats.Type {
case "recv": // 只统计进站流量
stats.Used = currentRecvTotal - stats.BaselineRecv
case "send": // 只统计出站流量
stats.Used = currentSentTotal - stats.BaselineSend
case "both": // 统计全部流量
recvUsed := currentRecvTotal - stats.BaselineRecv
sendUsed := currentSentTotal - stats.BaselineSend
stats.Used = recvUsed + sendUsed
default: // 默认只统计进站流量
stats.Used = currentRecvTotal - stats.BaselineRecv
}
}
// 检查告警(如果配置了限额)
@@ -153,3 +181,252 @@ func formatBytes(bytes uint64) string {
}
return fmt.Sprintf("%.2f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// UpdateTrafficConfig 更新流量配置
// used: 如果大于0,则使用该值作为已使用流量;如果为0,则保持当前值或重置
func (s *TrafficService) UpdateTrafficConfig(ctx context.Context, agentID string, enabled bool, trafficType string, limit uint64, resetDay int, used uint64) error {
if resetDay < 0 || resetDay > 31 {
return fmt.Errorf("重置日期必须在0-31之间")
}
// 验证流量类型
if trafficType != "" && trafficType != "recv" && trafficType != "send" && trafficType != "both" {
return fmt.Errorf("流量类型必须是 recv、send 或 both")
}
agent, err := s.agentRepo.FindById(ctx, agentID)
if err != nil {
return err
}
now := time.Now().UnixMilli()
stats := agent.TrafficStats.Data()
oldResetDay := stats.ResetDay
oldEnabled := stats.Enabled
oldType := stats.Type
// 设置配置
stats.Enabled = enabled
stats.Limit = limit
stats.ResetDay = resetDay
// 设置流量类型(默认为 recv)
if trafficType != "" {
stats.Type = trafficType
} else if stats.Type == "" {
stats.Type = "recv" // 默认只统计进站流量
}
// 如果是首次启用、禁用后重新启用、修改重置日期或修改流量类型,重置流量统计
if (!oldEnabled && stats.Enabled) || (stats.Enabled && resetDay != oldResetDay) || (stats.Enabled && stats.PeriodStart == 0) || (stats.Enabled && oldType != stats.Type && oldType != "") {
stats.Used = 0
stats.PeriodStart = now
stats.BaselineRecv = 0 // 下次上报时会设置正确的基线
stats.BaselineSend = 0
stats.AlertSent80 = false
stats.AlertSent90 = false
stats.AlertSent100 = false
}
// 如果提供了 used 参数且大于 0,则使用该值
if used > 0 {
stats.Used = used
}
// 如果禁用流量统计,清空相关数据
if !stats.Enabled {
stats.Used = 0
stats.PeriodStart = 0
stats.BaselineRecv = 0
stats.BaselineSend = 0
stats.AlertSent80 = false
stats.AlertSent90 = false
stats.AlertSent100 = false
}
agent.TrafficStats = datatypes.NewJSONType(stats)
agent.UpdatedAt = now
return s.agentRepo.UpdateById(ctx, &agent)
}
// GetTrafficStats 获取流量统计信息
func (s *TrafficService) GetTrafficStats(ctx context.Context, agentID string) (*TrafficStats, error) {
agent, err := s.agentRepo.FindById(ctx, agentID)
if err != nil {
return nil, err
}
trafficData := agent.TrafficStats.Data()
// 设置默认类型
trafficType := trafficData.Type
if trafficType == "" {
trafficType = "recv" // 默认只统计进站流量
}
stats := &TrafficStats{
Enabled: trafficData.Enabled,
Type: trafficType,
Limit: trafficData.Limit,
Used: trafficData.Used,
ResetDay: trafficData.ResetDay,
PeriodStart: trafficData.PeriodStart,
Alerts: TrafficAlerts{
Sent80: trafficData.AlertSent80,
Sent90: trafficData.AlertSent90,
Sent100: trafficData.AlertSent100,
},
}
// 计算使用百分比
if trafficData.Limit > 0 {
stats.UsedPercent = float64(trafficData.Used) / float64(trafficData.Limit) * 100
if trafficData.Used < trafficData.Limit {
stats.Remaining = trafficData.Limit - trafficData.Used
} else {
stats.Remaining = 0
}
}
// 计算下次重置日期和剩余天数
if trafficData.ResetDay > 0 && trafficData.PeriodStart > 0 {
periodStart := time.UnixMilli(trafficData.PeriodStart)
nextReset := calculateNextResetDate(periodStart, trafficData.ResetDay)
stats.PeriodEnd = nextReset.UnixMilli()
stats.DaysUntilReset = int(time.Until(nextReset).Hours() / 24)
if stats.DaysUntilReset < 0 {
stats.DaysUntilReset = 0
}
}
return stats, nil
}
// ResetAgentTraffic 重置探针流量
func (s *TrafficService) ResetAgentTraffic(ctx context.Context, agentID string) error {
agent, err := s.agentRepo.FindById(ctx, agentID)
if err != nil {
return err
}
now := time.Now().UnixMilli()
stats := agent.TrafficStats.Data()
stats.Used = 0
stats.BaselineRecv = 0 // 下次上报时会设置正确的基线
stats.BaselineSend = 0
stats.PeriodStart = now
stats.AlertSent80 = false
stats.AlertSent90 = false
stats.AlertSent100 = false
agent.TrafficStats = datatypes.NewJSONType(stats)
agent.UpdatedAt = now
s.logger.Info("探针流量已重置",
zap.String("agentId", agentID),
zap.String("agentName", agent.Name))
return s.agentRepo.UpdateById(ctx, &agent)
}
// CheckAndResetTraffic 检查并重置所有到期的探针流量(定时任务调用)
func (s *TrafficService) CheckAndResetTraffic(ctx context.Context) error {
agents, err := s.agentRepo.FindAgentsWithTrafficReset(ctx)
if err != nil {
return err
}
now := time.Now()
resetCount := 0
for _, agent := range agents {
if s.shouldResetTraffic(&agent, now) {
if err := s.ResetAgentTraffic(ctx, agent.ID); err != nil {
s.logger.Error("重置探针流量失败",
zap.String("agentId", agent.ID),
zap.Error(err))
continue
}
resetCount++
}
}
if resetCount > 0 {
s.logger.Info("流量重置检查完成", zap.Int("重置数量", resetCount))
}
return nil
}
// shouldResetTraffic 判断是否需要重置流量
func (s *TrafficService) shouldResetTraffic(agent *models.Agent, now time.Time) bool {
stats := agent.TrafficStats.Data()
// 流量统计未启用、未配置重置日期或周期未开始时不重置
if !stats.Enabled || stats.ResetDay == 0 || stats.PeriodStart == 0 {
return false
}
periodStart := time.UnixMilli(stats.PeriodStart)
nextReset := calculateNextResetDate(periodStart, stats.ResetDay)
return now.After(nextReset) || now.Equal(nextReset)
}
// calculateNextResetDate 计算下次重置日期
func calculateNextResetDate(periodStart time.Time, resetDay int) time.Time {
year, month, day := periodStart.Date()
location := periodStart.Location()
// 先计算当月的重置日期
currentMonthLastDay := time.Date(year, month+1, 0, 0, 0, 0, 0, location).Day()
currentMonthResetDay := resetDay
if resetDay > currentMonthLastDay {
currentMonthResetDay = currentMonthLastDay
}
currentMonthReset := time.Date(year, month, currentMonthResetDay, 0, 0, 0, 0, location)
// 如果当月的重置日期还没到(在 periodStart 之后或同一天),返回当月重置日期
if day < currentMonthResetDay || (day == currentMonthResetDay && periodStart.Before(currentMonthReset)) {
return currentMonthReset
}
// 当月重置日期已过,计算下个月的重置日期
nextMonth := month + 1
nextYear := year
if nextMonth > 12 {
nextMonth = 1
nextYear++
}
// 处理月末日期(如2月没有31号)
nextMonthLastDay := time.Date(nextYear, nextMonth+1, 0, 0, 0, 0, 0, location).Day()
nextMonthResetDay := resetDay
if resetDay > nextMonthLastDay {
nextMonthResetDay = nextMonthLastDay
}
return time.Date(nextYear, nextMonth, nextMonthResetDay, 0, 0, 0, 0, location)
}
// TrafficStats 流量统计信息
type TrafficStats struct {
Enabled bool `json:"enabled"`
Type string `json:"type"` // 统计类型: recv/send/both
Limit uint64 `json:"limit"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
Remaining uint64 `json:"remaining"`
ResetDay int `json:"resetDay"`
PeriodStart int64 `json:"periodStart"`
PeriodEnd int64 `json:"periodEnd"`
DaysUntilReset int `json:"daysUntilReset"`
Alerts TrafficAlerts `json:"alerts"`
}
// TrafficAlerts 流量告警状态
type TrafficAlerts struct {
Sent80 bool `json:"sent80"`
Sent90 bool `json:"sent90"`
Sent100 bool `json:"sent100"`
}
+1
View File
@@ -74,6 +74,7 @@ type AppComponents struct {
SSHLoginHandler *handler.SSHLoginHandler
AgentService *service.AgentService
TrafficService *service.TrafficService
MetricService *service.MetricService
AlertService *service.AlertService
PropertyService *service.PropertyService
+3 -1
View File
@@ -41,7 +41,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App
tamperService := service.NewTamperService(logger, db, manager)
ddnsService := service.NewDDNSService(logger, db, propertyService, manager)
sshLoginService := service.NewSSHLoginService(logger, db, manager, geoIPService)
agentHandler := handler.NewAgentHandler(logger, agentService, metricService, monitorService, tamperService, ddnsService, sshLoginService, manager)
agentHandler := handler.NewAgentHandler(logger, agentService, trafficService, metricService, monitorService, tamperService, ddnsService, sshLoginService, manager)
apiKeyHandler := handler.NewApiKeyHandler(logger, apiKeyService)
notifier := service.NewNotifier(logger)
alertService := service.NewAlertService(logger, db, propertyService, monitorService, notifier)
@@ -64,6 +64,7 @@ func InitializeApp(logger *zap.Logger, db *gorm.DB, cfg *config.AppConfig) (*App
DDNSHandler: ddnsHandler,
SSHLoginHandler: sshLoginHandler,
AgentService: agentService,
TrafficService: trafficService,
MetricService: metricService,
AlertService: alertService,
PropertyService: propertyService,
@@ -94,6 +95,7 @@ type AppComponents struct {
SSHLoginHandler *handler.SSHLoginHandler
AgentService *service.AgentService
TrafficService *service.TrafficService
MetricService *service.MetricService
AlertService *service.AlertService
PropertyService *service.PropertyService
+1 -1
View File
@@ -119,7 +119,7 @@ const AgentDetail = () => {
<Descriptions.Item label="探针名称">{agent?.name}</Descriptions.Item>
<Descriptions.Item label="探针ID">{agent?.id}</Descriptions.Item>
<Descriptions.Item label="主机名">{agent?.hostname}</Descriptions.Item>
<Descriptions.Item label="IP地址">{agent?.ip}</Descriptions.Item>
<Descriptions.Item label="通信IP">{agent?.ip}</Descriptions.Item>
<Descriptions.Item label="操作系统">
<Tag color="blue">{agent?.os}</Tag>
</Descriptions.Item>
@@ -16,6 +16,7 @@ const TrafficStats: React.FC<TrafficStatsProps> = ({agentId}) => {
const [form] = Form.useForm();
const queryClient = useQueryClient();
const [enabled, setEnabled] = useState(false);
const [editingUsed, setEditingUsed] = useState(false);
// 获取流量统计
const {data: stats, isLoading} = useQuery<TrafficStatsType>({
@@ -32,14 +33,18 @@ const TrafficStats: React.FC<TrafficStatsProps> = ({agentId}) => {
mutationFn: async () => {
const values = form.getFieldsValue();
const limitBytes = enabled ? (values.trafficLimit || 0) * 1024 * 1024 * 1024 : 0;
const usedBytes = editingUsed && values.trafficUsed ? values.trafficUsed * 1024 * 1024 * 1024 : 0;
return updateTrafficConfig(agentId, {
enabled: enabled,
type: values.trafficType || 'recv',
limit: limitBytes,
used: usedBytes,
resetDay: enabled ? (values.trafficResetDay || 0) : 0,
});
},
onSuccess: () => {
message.success('配置已保存');
setEditingUsed(false);
queryClient.invalidateQueries({queryKey: ['trafficStats', agentId]});
},
onError: (error: unknown) => {
@@ -80,13 +85,17 @@ const TrafficStats: React.FC<TrafficStatsProps> = ({agentId}) => {
// 从服务器返回的 enabled 字段读取启用状态
setEnabled(stats.enabled);
form.setFieldsValue({
trafficType: stats.type || 'recv',
trafficLimit: stats.limit > 0 ? stats.limit / (1024 * 1024 * 1024) : 0,
trafficUsed: stats.used > 0 ? stats.used / (1024 * 1024 * 1024) : 0,
trafficResetDay: stats.resetDay || 0,
});
} else {
setEnabled(false);
form.setFieldsValue({
trafficType: 'recv',
trafficLimit: 0,
trafficUsed: 0,
trafficResetDay: 0,
});
}
@@ -217,7 +226,9 @@ const TrafficStats: React.FC<TrafficStatsProps> = ({agentId}) => {
form={form}
layout="vertical"
initialValues={{
trafficType: 'recv',
trafficLimit: 0,
trafficUsed: 0,
trafficResetDay: 0,
}}
>
@@ -235,6 +246,22 @@ const TrafficStats: React.FC<TrafficStatsProps> = ({agentId}) => {
{enabled && (
<>
<Form.Item
label="统计类型"
name="trafficType"
rules={[{required: true, message: '请选择统计类型'}]}
extra="选择要统计的流量类型"
>
<Select
placeholder="请选择统计类型"
options={[
{label: '进站流量 (下载)', value: 'recv'},
{label: '出站流量 (上传)', value: 'send'},
{label: '全部流量 (上传+下载)', value: 'both'},
]}
/>
</Form.Item>
<Form.Item
label="流量限额"
name="trafficLimit"
@@ -251,6 +278,33 @@ const TrafficStats: React.FC<TrafficStatsProps> = ({agentId}) => {
/>
</Form.Item>
<Form.Item
label={
<div className="flex items-center gap-2">
<span>已使用流量</span>
<Switch
size="small"
checked={editingUsed}
onChange={setEditingUsed}
checkedChildren="编辑中"
unCheckedChildren="锁定"
/>
</div>
}
name="trafficUsed"
extra={editingUsed ? "手动设置已使用的流量大小(GB)" : "当前已使用的流量(只读)"}
>
<InputNumber
min={0}
step={0.1}
precision={2}
placeholder="已使用流量(GB)"
style={{width: '100%'}}
addonAfter="GB"
disabled={!editingUsed}
/>
</Form.Item>
<Form.Item
label="流量重置日期"
name="trafficResetDay"
+6 -1
View File
@@ -46,11 +46,13 @@ export interface TrafficData {
export interface TrafficStatsData {
enabled: boolean;
type: string; // 统计类型: recv进站/send出站/both全部
limit: number; // 流量限额(字节), 0表示不限制
used: number; // 当前周期已使用流量(字节)
resetDay: number; // 流量重置日期(1-31), 0表示不自动重置
periodStart: number; // 当前周期开始时间(时间戳毫秒)
baselineRecv: number; // 当前周期流量基线
baselineRecv: number; // 当前周期流量基线(接收)
baselineSend: number; // 当前周期流量基线(发送)
alertSent80: boolean;
alertSent90: boolean;
alertSent100: boolean;
@@ -494,6 +496,7 @@ export interface TrafficAlerts {
export interface TrafficStats {
enabled: boolean;
type: string; // 统计类型: recv进站/send出站/both全部
limit: number;
used: number;
usedPercent: number;
@@ -507,7 +510,9 @@ export interface TrafficStats {
export interface UpdateTrafficConfigRequest {
enabled: boolean; // 是否启用
type: string; // 统计类型: recv进站/send出站/both全部
limit: number; // 流量限额(字节), 0表示不限制
used?: number; // 手动设置已使用流量(可选)
resetDay: number; // 流量重置日期(1-31), 0表示不自动重置
}