Files
meshtastic_mqtt_server/internal/web/mqtt_status.go
T
kevinandClaude 9394aa0f4a 重构:拆出 bot / sign / mapsource / llmadmin / web 包
第三批(最终批):把 root 中剩下所有领域文件按功能搬到 internal/ 下。
完成后根目录从 46 个 .go 文件降到 14 个,其中 11 个是 bridge(type alias
+ thin wrapper),仅供尚未改造的 main.go 引用。

新增包
- internal/bot/         Service / TextSender / NewPKIKeyResolver /
                        RegisterRoutes,含 PKI 直连发送、节点信息广播、
                        outbound DM 持久化等业务逻辑。
- internal/sign/        SignDTO / SignDayCountDTO / RegisterAdminRoutes,
                        把原来分散在 admin_sign_routes.go 与 web.go 中的
                        sign DTO/路由收拢到一处。
- internal/mapsource/   AdminDTO / PublicDTO / RegisterAdminRoutes /
                        RegisterPublicRoutes。
- internal/llmadmin/    LLM 消息队列、Provider、ToolRouter、PrimaryConfig 的
                        admin 路由。
- internal/web/         路由总入口(NewRouter/NewHTTPServer/ServeUnixSocket)、
                        各资源的 GET API、admin 用户/登录/MQTT 状态、所有
                        DTO 函数。把 auth.go 的 sessionClaims 升级为
                        auth.SessionClaims;mqtt_status.go 重写成
                        MQTTRuntimeStatus / AdminMQTTStatus 结构体并把
                        client info 解析在 web 包内自带,不再依赖 main 包。
                        map_tile_proxy_routes 与测试一起搬入。

修改
- web.go 中 parseListOptions / writeListResponse / ptrString 等本地 helper
  改为对 internal/webutil 的 thin wrapper,避免重复实现。
- internal/auth 在 step 4 已创建,本批中 web 包正式开始引用其 Manager /
  RequireAdmin / SessionClaims。

根目录新增 bridge:bot_bridge.go / sign_bridge.go / mapsource_bridge.go /
llmadmin_bridge.go / web_bridge.go。后者把 mqttRuntimeStatus 包成
webpkg.MQTTStatusProvider 适配器,使 main.go 中旧字段名保持可用。

go build ./... / go test ./... 全部通过;测试数量未变。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-18 18:24:14 +08:00

151 lines
5.1 KiB
Go

package web
import (
mqtt "github.com/mochi-mqtt/server/v2"
mqttforwardpkg "meshtastic_mqtt_server/internal/mqttforward"
storepkg "meshtastic_mqtt_server/internal/store"
)
// MQTTStatusProvider 是 web 层向上层要的"返回当前 mqtt broker 状态"接口;
// 实现一般由 main 包传入(持有真正的 mqtt.Server / 写队列 / 统计器)。
type MQTTStatusProvider interface {
Status() AdminMQTTStatus
}
// MQTTRuntimeStatus 把 mqtt.Server / 写队列 / 转发统计三个上下文打包成
// 实现 MQTTStatusProvider 的具体类型。供 main 包构造后注入 newRouter。
type MQTTRuntimeStatus struct {
Server *mqtt.Server
Address string
TLS bool
Stats *mqttforwardpkg.Stats
DBQueue *storepkg.WriteQueue
}
// AdminMQTTStatus 是 admin 路由 GET /admin/mqtt-status 返回的 JSON 视图。
type AdminMQTTStatus struct {
Running bool `json:"running"`
Address string `json:"address"`
TLS bool `json:"tls"`
Version string `json:"version"`
Started int64 `json:"started"`
Uptime int64 `json:"uptime"`
BytesReceived int64 `json:"bytes_received"`
BytesSent int64 `json:"bytes_sent"`
ClientsConnected int64 `json:"clients_connected"`
ClientsDisconnected int64 `json:"clients_disconnected"`
ClientsMaximum int64 `json:"clients_maximum"`
ClientsTotal int64 `json:"clients_total"`
MessagesReceived int64 `json:"messages_received"`
MessagesSent int64 `json:"messages_sent"`
MessagesDropped int64 `json:"messages_dropped"`
DBWriteQueueLength int `json:"db_write_queue_length"`
Retained int64 `json:"retained"`
Inflight int64 `json:"inflight"`
InflightDropped int64 `json:"inflight_dropped"`
Subscriptions int64 `json:"subscriptions"`
PacketsReceived int64 `json:"packets_received"`
PacketsSent int64 `json:"packets_sent"`
Clients []AdminMQTTClient `json:"clients"`
}
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"`
}
// Status 实现 MQTTStatusProvider。
func (m MQTTRuntimeStatus) Status() AdminMQTTStatus {
if m.Server == nil || m.Server.Info == nil {
return AdminMQTTStatus{Running: false, Address: m.Address, TLS: m.TLS, DBWriteQueueLength: m.DBQueue.Len()}
}
info := m.Server.Info.Clone()
status := AdminMQTTStatus{
Running: true,
Address: m.Address,
TLS: m.TLS,
Version: info.Version,
Started: info.Started,
Uptime: info.Uptime,
BytesReceived: info.BytesReceived,
BytesSent: info.BytesSent,
ClientsConnected: info.ClientsConnected,
ClientsDisconnected: info.ClientsDisconnected,
ClientsMaximum: info.ClientsMaximum,
ClientsTotal: info.ClientsTotal,
MessagesReceived: info.MessagesReceived,
MessagesSent: m.Stats.Forwarded(),
MessagesDropped: m.Stats.Dropped(),
DBWriteQueueLength: m.DBQueue.Len(),
Retained: info.Retained,
Inflight: info.Inflight,
InflightDropped: info.InflightDropped,
Subscriptions: info.Subscriptions,
PacketsReceived: info.PacketsReceived,
PacketsSent: info.PacketsSent,
}
for _, client := range m.Server.Clients.GetAll() {
if client == nil || client.Closed() {
continue
}
info := mqttClientInfo(client)
status.Clients = append(status.Clients, AdminMQTTClient{
ClientID: info.ClientID,
Username: info.Username,
Listener: info.Listener,
RemoteAddr: info.RemoteAddr,
RemoteHost: info.RemoteHost,
RemotePort: info.RemotePort,
})
}
return status
}
// 简化版客户端信息——只解析展示所需字段,避免依赖 main 包里的辅助。
type mqttClientInfoView struct {
ClientID string
Username string
Listener string
RemoteAddr string
RemoteHost string
RemotePort string
}
func mqttClientInfo(c *mqtt.Client) mqttClientInfoView {
if c == nil {
return mqttClientInfoView{}
}
info := 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, ""
}