feat(v2): webhook 投递硬化——指数退避 + 最大次数死信 + 告警钩子(替裸 60s 猛敲)

This commit is contained in:
wangjia
2026-07-10 17:25:21 +08:00
parent 6427e8c848
commit c73f7ffaa9
5 changed files with 227 additions and 16 deletions
+45 -1
View File
@@ -2,6 +2,7 @@ package store
import (
"fmt"
"time"
"unicode/utf8"
"gorm.io/gorm"
@@ -67,7 +68,8 @@ func truncateUTF8(s string, n int) string {
}
// MarkFailed increments attempts and records the last error, leaving the row
// undelivered for the next retry sweep.
// undelivered for the next retry sweep. Superseded in production by
// ScheduleRetry/MarkDead (退避感知);kept for existing callers/tests.
func (s *WebhookStore) MarkFailed(id uint64, errMsg string) error {
errMsg = truncateUTF8(errMsg, 255)
if err := s.db.Model(&model.WebhookDelivery{}).Where("id = ?", id).
@@ -79,3 +81,45 @@ func (s *WebhookStore) MarkFailed(id uint64, errMsg string) error {
}
return nil
}
// ListDeliverable 取「未投递、未死信、且退避到点(next_attempt_at NULL 或 <= now)」的行。
func (s *WebhookStore) ListDeliverable(now time.Time, limit int) ([]WebhookDeliveryRow, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
var out []WebhookDeliveryRow
if err := s.db.
Where("delivered = ? AND dead = ? AND (next_attempt_at IS NULL OR next_attempt_at <= ?)", false, false, now).
Order("id ASC").Limit(limit).Find(&out).Error; err != nil {
return nil, fmt.Errorf("store.ListDeliverable: %w", err)
}
return out, nil
}
// ScheduleRetry 记一次失败并排下次重试:attempts+1、last_error、next_attempt_at=nextAt。
func (s *WebhookStore) ScheduleRetry(id uint64, errMsg string, nextAt time.Time) error {
errMsg = truncateUTF8(errMsg, 255)
if err := s.db.Model(&model.WebhookDelivery{}).Where("id = ?", id).
Updates(map[string]any{
"attempts": gorm.Expr("attempts + 1"),
"last_error": errMsg,
"next_attempt_at": nextAt,
}).Error; err != nil {
return fmt.Errorf("store.ScheduleRetry: %w", err)
}
return nil
}
// MarkDead 达最大次数后放弃:attempts+1、dead=true、last_error。行留库供人工/对账排查。
func (s *WebhookStore) MarkDead(id uint64, errMsg string) error {
errMsg = truncateUTF8(errMsg, 255)
if err := s.db.Model(&model.WebhookDelivery{}).Where("id = ?", id).
Updates(map[string]any{
"attempts": gorm.Expr("attempts + 1"),
"dead": true,
"last_error": errMsg,
}).Error; err != nil {
return fmt.Errorf("store.MarkDead: %w", err)
}
return nil
}