Files
Cyrene_v2/internal/services/backthink/event_dedup.go
T
Aska d09b25071d
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(backthink): 事件驱动主动消息绕过PushGate——IoT等事件该发就发(heartbeat仍受60min节流)
- trigger.go: 新增 TriggerKind(TriggerHeartbeat/TriggerEvent) + Trigger.Kind 标记触发来源
- push.go: PushRequest.TriggerKind 分流——event 跳过 gate.Allow/RecordPush(该发就发),heartbeat 仍受 60min≤1/冷却/分类限频;PushResult.Bypassed 标记 + detail=event_bypass 区分
- backthink.go: think() 传 TriggerKind + 过同事件去重门
- event_dedup.go(新): 同事件(Source+Summary)去重防刷,无 60min 节流;不同事件/心跳后重置链
- 测试: event 连续 3 次全推 bypass / heartbeat 第2次 rate_limited / event 不消耗心跳限 / 同事件去重
- build/vet/test 全绿 + windows/darwin 交叉编译通过
2026-09-08 13:46:43 +08:00

32 lines
1.2 KiB
Go

package backthink
// eventRedundant reports whether tr is a duplicate of the immediately preceding
// event-driven push (same source + summary), so a repeating device/reminder event
// does not spam the owner (防刷). It is the one guard that still applies to an
// event-driven push while the PushGate itself is bypassed for events (owner
// mandate: "该发就发" — a genuine event goes out, only a true duplicate is held).
//
// It atomically records tr as the latest event key when the push proceeds, and
// clears the key on any non-event push (heartbeat) so a later repeat of a prior
// event is not spuriously suppressed by an intervening reflection.
//
// The caller runs it before handing the push to the Pusher; a true duplicate is
// recorded as suppressed (RouteNone) rather than delivered.
func (b *Backthink) eventRedundant(tr Trigger) bool {
if tr.Kind != TriggerEvent {
// A non-event push (heartbeat) breaks the same-event chain.
b.eventMu.Lock()
b.lastEventKey = ""
b.eventMu.Unlock()
return false
}
key := tr.Source + "\x00" + tr.Summary
b.eventMu.Lock()
defer b.eventMu.Unlock()
if b.lastEventKey == key {
return true
}
b.lastEventKey = key
return false
}