feat(v2): webhook 投递硬化——指数退避 + 最大次数死信 + 告警钩子(替裸 60s 猛敲)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user