feat: 管理后台仪表盘统计卡片接入真实数据
新增 /api/admin/stats 聚合接口,填充运行时长、活跃设备、今日流量、在线节点四项卡片: - VpnService 记录启动时间,提供 StartedAt/TotalLiveTraffic 方法 - 新增 TrafficStat 模型,客户端断开时 upsert 当日 rx/tx 流量(持久化) - 前端 AdminView 调用 stats 接口并每 30 秒自动刷新
This commit is contained in:
+35
-12
@@ -8,9 +8,12 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"lmvpn/internal/db"
|
||||
"lmvpn/internal/model"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,19 +31,19 @@ var (
|
||||
)
|
||||
|
||||
type tunnelConn struct {
|
||||
conn *websocket.Conn
|
||||
user *model.User
|
||||
svc *VpnService
|
||||
assignedIP net.IP
|
||||
assignedIP6 net.IP
|
||||
connectedAt time.Time
|
||||
writeMu sync.Mutex
|
||||
ready atomic.Bool
|
||||
rxBytes atomic.Int64
|
||||
txBytes atomic.Int64
|
||||
conn *websocket.Conn
|
||||
user *model.User
|
||||
svc *VpnService
|
||||
assignedIP net.IP
|
||||
assignedIP6 net.IP
|
||||
connectedAt time.Time
|
||||
writeMu sync.Mutex
|
||||
ready atomic.Bool
|
||||
rxBytes atomic.Int64
|
||||
txBytes atomic.Int64
|
||||
}
|
||||
|
||||
func (c *tunnelConn) AssignedIP() net.IP { return c.assignedIP }
|
||||
func (c *tunnelConn) AssignedIP() net.IP { return c.assignedIP }
|
||||
func (c *tunnelConn) AssignedIP6() net.IP { return c.assignedIP6 }
|
||||
|
||||
func (c *tunnelConn) WritePacket(data []byte) error {
|
||||
@@ -128,7 +131,10 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
}
|
||||
|
||||
VPN.registerClient(tc)
|
||||
defer VPN.unregisterClient(tc)
|
||||
defer func() {
|
||||
recordTraffic(tc.rxBytes.Load(), tc.txBytes.Load())
|
||||
VPN.unregisterClient(tc)
|
||||
}()
|
||||
|
||||
settings := VPN.Settings()
|
||||
initMsg := initMessage{
|
||||
@@ -222,3 +228,20 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func recordTraffic(rx, tx int64) {
|
||||
if rx == 0 && tx == 0 {
|
||||
return
|
||||
}
|
||||
today := time.Now().Format("2006-01-02")
|
||||
stat := model.TrafficStat{Date: today, RxBytes: rx, TxBytes: tx}
|
||||
if err := db.DB.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "date"}},
|
||||
DoUpdates: clause.Assignments(map[string]interface{}{
|
||||
"rx_bytes": gorm.Expr("rx_bytes + ?", rx),
|
||||
"tx_bytes": gorm.Expr("tx_bytes + ?", tx),
|
||||
}),
|
||||
}).Create(&stat).Error; err != nil {
|
||||
log.Printf("记录流量失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user