feat: console_log.meshtastic 控制 packet 控制台输出;新增每客户端报文统计

- ConsoleLogConfig 增加 Meshtastic 字段(默认 true);旧配置自动补齐
- meshtasticFilterHook 增加 packetConsoleLog;OnPublish 中根据开关调用
  新的 printMeshtasticRecord 输出可读单行(key=value、按 type 着色),
  替代原来的 JSON dump;事件型 printJSON 调用保持不变
- 新增 internal/mqttforward/ClientStats,按 client_id 累计 in/out 报文计数
- meshtasticFilterHook 多挂 OnPacketRead / OnPacketSent / OnDisconnect
  事件,用于增减计数;Provides() 同步声明
- AdminMQTTClient JSON 视图删除 RemoteHost/RemotePort,新增
  packets_in / packets_out;前端 types.ts、AdminDashboard.vue 同步更新

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-19 17:09:00 +08:00
co-authored by Claude
parent 937580e24f
commit 49a287e6e7
7 changed files with 237 additions and 80 deletions
+16 -37
View File
@@ -16,11 +16,12 @@ type MQTTStatusProvider interface {
// MQTTRuntimeStatus 把 mqtt.Server / 写队列 / 转发统计三个上下文打包成
// 实现 MQTTStatusProvider 的具体类型。供 main 包构造后注入 newRouter。
type MQTTRuntimeStatus struct {
Server *mqtt.Server
Address string
TLS bool
Stats *mqttforwardpkg.Stats
DBQueue *storepkg.WriteQueue
Server *mqtt.Server
Address string
TLS bool
Stats *mqttforwardpkg.Stats
ClientStats *mqttforwardpkg.ClientStats
DBQueue *storepkg.WriteQueue
}
// AdminMQTTStatus 是 admin 路由 GET /admin/mqtt-status 返回的 JSON 视图。
@@ -51,12 +52,12 @@ type AdminMQTTStatus struct {
}
type AdminMQTTClient struct {
ClientID string `json:"client_id"`
Username string `json:"username"`
Listener string `json:"listener"`
RemoteAddr string `json:"remote_addr"`
RemoteHost string `json:"remote_host"`
RemotePort string `json:"remote_port"`
ClientID string `json:"client_id"`
Username string `json:"username"`
Listener string `json:"listener"`
RemoteAddr string `json:"remote_addr"`
PacketsIn int64 `json:"packets_in"` // 客户端 → 服务器
PacketsOut int64 `json:"packets_out"` // 服务器 → 客户端
}
// Status 实现 MQTTStatusProvider。
@@ -94,13 +95,14 @@ func (m MQTTRuntimeStatus) Status() AdminMQTTStatus {
continue
}
info := mqttClientInfo(client)
in, out := m.ClientStats.Get(info.ClientID)
status.Clients = append(status.Clients, AdminMQTTClient{
ClientID: info.ClientID,
Username: info.Username,
Listener: info.Listener,
RemoteAddr: info.RemoteAddr,
RemoteHost: info.RemoteHost,
RemotePort: info.RemotePort,
PacketsIn: in,
PacketsOut: out,
})
}
return status
@@ -112,39 +114,16 @@ type mqttClientInfoView struct {
Username string
Listener string
RemoteAddr string
RemoteHost string
RemotePort string
}
func mqttClientInfo(c *mqtt.Client) mqttClientInfoView {
if c == nil {
return mqttClientInfoView{}
}
info := mqttClientInfoView{
return mqttClientInfoView{
ClientID: c.ID,
Username: string(c.Properties.Username),
Listener: c.Net.Listener,
RemoteAddr: c.Net.Remote,
}
host, port := splitHostPort(c.Net.Remote)
info.RemoteHost = host
info.RemotePort = port
return info
}
func splitHostPort(addr string) (string, string) {
if addr == "" {
return "", ""
}
// 复用 net.SplitHostPort,但要兼容 "host" 这种没端口的情况。
for i := len(addr) - 1; i >= 0; i-- {
if addr[i] == ':' {
host := addr[:i]
if len(host) >= 2 && host[0] == '[' && host[len(host)-1] == ']' {
host = host[1 : len(host)-1]
}
return host, addr[i+1:]
}
}
return addr, ""
}