2 Commits

Author SHA1 Message Date
Flik
e40d079f7a feat(server): add traffic storage and statistics tracking for improved traffic management
Some checks failed
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Has been cancelled
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Has been cancelled
Build Multi-Platform Binaries / build-frontend (push) Has been cancelled
2026-01-29 15:38:27 +08:00
Flik
8ce5b149f7 fix(server): update client nickname handling to prevent overwriting manual names
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 29s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m35s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m25s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m50s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m15s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m40s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m44s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m4s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m16s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m46s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m19s
2026-01-29 15:08:04 +08:00
4 changed files with 47 additions and 11 deletions

View File

@@ -84,6 +84,7 @@ func main() {
registry := plugin.NewRegistry() registry := plugin.NewRegistry()
server.SetPluginRegistry(registry) server.SetPluginRegistry(registry)
server.SetJSPluginStore(clientStore) // 设置 JS 插件存储,用于客户端重连时恢复插件 server.SetJSPluginStore(clientStore) // 设置 JS 插件存储,用于客户端重连时恢复插件
server.SetTrafficStore(clientStore) // 设置流量存储,用于记录流量统计
// 启动 Web 控制台 // 启动 Web 控制台
if cfg.Server.Web.Enabled { if cfg.Server.Web.Enabled {

View File

@@ -397,12 +397,17 @@ func (s *SQLiteStore) Get24HourTraffic() (inbound, outbound int64, err error) {
return return
} }
// GetHourlyTraffic 获取每小时流量记录 // GetHourlyTraffic 获取每小时流量记录(始终返回完整的 hours 小时数据)
func (s *SQLiteStore) GetHourlyTraffic(hours int) ([]TrafficRecord, error) { func (s *SQLiteStore) GetHourlyTraffic(hours int) ([]TrafficRecord, error) {
s.mu.RLock() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
cutoff := time.Now().Add(-time.Duration(hours) * time.Hour).Unix() // 计算当前小时的起始时间戳
now := time.Now()
currentHour := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
// 查询数据库中的记录
cutoff := currentHour.Add(-time.Duration(hours-1) * time.Hour).Unix()
rows, err := s.db.Query(` rows, err := s.db.Query(`
SELECT hour_ts, inbound, outbound FROM traffic_stats SELECT hour_ts, inbound, outbound FROM traffic_stats
WHERE hour_ts >= ? ORDER BY hour_ts ASC WHERE hour_ts >= ? ORDER BY hour_ts ASC
@@ -412,13 +417,26 @@ func (s *SQLiteStore) GetHourlyTraffic(hours int) ([]TrafficRecord, error) {
} }
defer rows.Close() defer rows.Close()
var records []TrafficRecord // 将数据库记录放入 map 以便快速查找
dbRecords := make(map[int64]TrafficRecord)
for rows.Next() { for rows.Next() {
var r TrafficRecord var r TrafficRecord
if err := rows.Scan(&r.Timestamp, &r.Inbound, &r.Outbound); err != nil { if err := rows.Scan(&r.Timestamp, &r.Inbound, &r.Outbound); err != nil {
return nil, err return nil, err
} }
records = append(records, r) dbRecords[r.Timestamp] = r
} }
// 生成完整的 hours 小时数据
records := make([]TrafficRecord, hours)
for i := 0; i < hours; i++ {
ts := currentHour.Add(-time.Duration(hours-1-i) * time.Hour).Unix()
if r, ok := dbRecords[ts]; ok {
records[i] = r
} else {
records[i] = TrafficRecord{Timestamp: ts, Inbound: 0, Outbound: 0}
}
}
return records, nil return records, nil
} }

View File

@@ -51,6 +51,7 @@ func generateClientID() string {
type Server struct { type Server struct {
clientStore db.ClientStore clientStore db.ClientStore
jsPluginStore db.JSPluginStore // JS 插件存储 jsPluginStore db.JSPluginStore // JS 插件存储
trafficStore db.TrafficStore // 流量存储
bindAddr string bindAddr string
bindPort int bindPort int
token string token string
@@ -161,6 +162,11 @@ func (s *Server) SetJSPluginStore(store db.JSPluginStore) {
s.jsPluginStore = store s.jsPluginStore = store
} }
// SetTrafficStore 设置流量存储
func (s *Server) SetTrafficStore(store db.TrafficStore) {
s.trafficStore = store
}
// LoadJSPlugins 加载 JS 插件配置 // LoadJSPlugins 加载 JS 插件配置
func (s *Server) LoadJSPlugins(plugins []JSPluginEntry) { func (s *Server) LoadJSPlugins(plugins []JSPluginEntry) {
s.jsPlugins = plugins s.jsPlugins = plugins
@@ -278,9 +284,10 @@ func (s *Server) handleConnection(conn net.Conn) {
} }
log.Printf("[Server] New client registered: %s (%s)", clientID, authReq.Name) log.Printf("[Server] New client registered: %s (%s)", clientID, authReq.Name)
} else if authReq.Name != "" { } else if authReq.Name != "" {
// 客户端已存在,更新名称(如果提供了新名称) // 客户端已存在,仅当 Nickname 为空时才用客户端名称更新
// 这样服务端手动设置的名称不会被客户端覆盖
if client, err := s.clientStore.GetClient(clientID); err == nil { if client, err := s.clientStore.GetClient(clientID); err == nil {
if client.Nickname == "" || client.Nickname != authReq.Name { if client.Nickname == "" {
client.Nickname = authReq.Name client.Nickname = authReq.Name
s.clientStore.UpdateClient(client) s.clientStore.UpdateClient(client)
} }
@@ -535,7 +542,7 @@ func (s *Server) handleProxyConn(cs *ClientSession, conn net.Conn, rule protocol
return return
} }
relay.Relay(conn, stream) relay.RelayWithStats(conn, stream, s.recordTraffic)
} }
// heartbeatLoop 心跳检测循环 // heartbeatLoop 心跳检测循环
@@ -1223,7 +1230,7 @@ func (s *Server) handleClientPluginConn(cs *ClientSession, conn net.Conn, rule p
} }
} }
relay.Relay(conn, stream) relay.RelayWithStats(conn, stream, s.recordTraffic)
} }
// checkHTTPBasicAuth 检查 HTTP Basic Auth // checkHTTPBasicAuth 检查 HTTP Basic Auth
@@ -1906,6 +1913,16 @@ func (s *Server) StopClientLogStream(sessionID string) {
s.logSessions.RemoveSession(sessionID) s.logSessions.RemoveSession(sessionID)
} }
// recordTraffic 记录流量统计
func (s *Server) recordTraffic(inbound, outbound int64) {
if s.trafficStore == nil {
return
}
if err := s.trafficStore.AddTraffic(inbound, outbound); err != nil {
log.Printf("[Server] Record traffic error: %v", err)
}
}
// boolPtr 返回 bool 值的指针 // boolPtr 返回 bool 值的指针
func boolPtr(b bool) *bool { func boolPtr(b bool) *bool {
return &b return &b

View File

@@ -142,5 +142,5 @@ func (s *Server) handleWebsocketProxyConn(cs *ClientSession, conn net.Conn, rule
return return
} }
relay.Relay(conn, stream) relay.RelayWithStats(conn, stream, s.recordTraffic)
} }