添加 MQTT 消息去重队列:Hook 层基于 payload+topic hash 去重,TTL 15 秒,定时清理

This commit is contained in:
2026-06-30 12:10:29 +08:00
parent ca59c5f316
commit 4782e84c15
2 changed files with 84 additions and 1 deletions
+72
View File
@@ -0,0 +1,72 @@
package mqttforward
import (
"crypto/sha256"
"encoding/hex"
"sync"
"time"
)
const dedupTTL = 15 * time.Second
type DedupQueue struct {
mu sync.Mutex
entries map[string]time.Time
stopCh chan struct{}
}
func NewDedupQueue() *DedupQueue {
return &DedupQueue{
entries: make(map[string]time.Time),
stopCh: make(chan struct{}),
}
}
func (dq *DedupQueue) TryForward(topic string, payload []byte) bool {
hash := dedupHash(topic, payload)
now := time.Now()
dq.mu.Lock()
defer dq.mu.Unlock()
if expiry, ok := dq.entries[hash]; ok && now.Before(expiry) {
return false
}
dq.entries[hash] = now.Add(dedupTTL)
return true
}
func (dq *DedupQueue) Start() {
go func() {
ticker := time.NewTicker(dedupTTL)
defer ticker.Stop()
for {
select {
case <-ticker.C:
dq.cleanup()
case <-dq.stopCh:
return
}
}
}()
}
func (dq *DedupQueue) Stop() {
close(dq.stopCh)
}
func (dq *DedupQueue) cleanup() {
now := time.Now()
dq.mu.Lock()
defer dq.mu.Unlock()
for hash, expiry := range dq.entries {
if now.After(expiry) {
delete(dq.entries, hash)
}
}
}
func dedupHash(topic string, payload []byte) string {
h := sha256.New()
h.Write([]byte(topic))
h.Write(payload)
return hex.EncodeToString(h.Sum(nil))
}
+11
View File
@@ -59,6 +59,7 @@ type meshtasticFilterHook struct {
autoAcker func(record map[string]any) autoAcker func(record map[string]any)
consoleLog bool // 控制台是否打印 MQTT 连接/订阅事件 consoleLog bool // 控制台是否打印 MQTT 连接/订阅事件
packetConsoleLog bool // 控制台是否打印 Meshtastic 数据包 packetConsoleLog bool // 控制台是否打印 Meshtastic 数据包
dedupQueue *mqttforwardpkg.DedupQueue
} }
// ID 返回用于识别 Meshtastic payload 过滤器的 hook 名称。 // ID 返回用于识别 Meshtastic payload 过滤器的 hook 名称。
@@ -225,6 +226,10 @@ func (h *meshtasticFilterHook) OnPublish(cl *mqtt.Client, pk packets.Packet) (pa
h.rejectPublish(cl, pk, record) h.rejectPublish(cl, pk, record)
return pk, packets.ErrRejectPacket return pk, packets.ErrRejectPacket
} }
if h.dedupQueue != nil && !h.dedupQueue.TryForward(pk.TopicName, pk.Payload) {
h.stats.IncDropped()
return pk, packets.ErrRejectPacket
}
h.stats.IncForwarded() h.stats.IncForwarded()
h.dbQueue.EnqueueRecord(record, mqttClientInfoFromClient(cl)) h.dbQueue.EnqueueRecord(record, mqttClientInfoFromClient(cl))
@@ -521,6 +526,9 @@ func run(cfg *configpkg.Config) error {
if err := server.Close(); err != nil && runErr == nil { if err := server.Close(); err != nil && runErr == nil {
runErr = err runErr = err
} }
if mqttHook.dedupQueue != nil {
mqttHook.dedupQueue.Stop()
}
return runErr return runErr
} }
@@ -529,6 +537,8 @@ func startMQTTServer(cfg *configpkg.Config, store *storepkg.Store, dbQueue *stor
if err := server.AddHook(new(mqttauth.AllowHook), nil); err != nil { if err := server.AddHook(new(mqttauth.AllowHook), nil); err != nil {
return nil, nil, "", err return nil, nil, "", err
} }
dedupQueue := mqttforwardpkg.NewDedupQueue()
dedupQueue.Start()
hook := &meshtasticFilterHook{ hook := &meshtasticFilterHook{
server: server, server: server,
key: cfg.Key, key: cfg.Key,
@@ -540,6 +550,7 @@ func startMQTTServer(cfg *configpkg.Config, store *storepkg.Store, dbQueue *stor
pkiResolver: botpkg.NewPKIKeyResolver(store), pkiResolver: botpkg.NewPKIKeyResolver(store),
consoleLog: cfg.ConsoleLog.MQTT, consoleLog: cfg.ConsoleLog.MQTT,
packetConsoleLog: cfg.ConsoleLog.Meshtastic, packetConsoleLog: cfg.ConsoleLog.Meshtastic,
dedupQueue: dedupQueue,
} }
if err := server.AddHook(hook, nil); err != nil { if err := server.AddHook(hook, nil); err != nil {
return nil, nil, "", err return nil, nil, "", err