89 lines
3.0 KiB
Go
89 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/segmentio/kafka-go"
|
|
"github.com/tinode/chat/server/logs"
|
|
"github.com/tinode/chat/server/store"
|
|
"github.com/tinode/chat/server/store/types"
|
|
)
|
|
|
|
func startKafkaDeadLetterConsumer() {
|
|
startKafkaConsumer("dead_letter", handleKafkaDeadLetter)
|
|
}
|
|
|
|
func handleKafkaDeadLetter(ctx context.Context, envelope kafkaEnvelope, _ kafka.Message) error {
|
|
var dead types.KafkaDeadLetter
|
|
if err := json.Unmarshal(envelope.Payload, &dead); err != nil {
|
|
return err
|
|
}
|
|
if dead.DeadID == "" {
|
|
dead.DeadID = envelope.EventID
|
|
}
|
|
tenantID, err := tenantIDFromDeadLetter(envelope, &dead)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
now := time.Now().UTC().Round(time.Millisecond)
|
|
if err = store.HTTPInfrastructure.MarkKafkaConsumeDead(tenantID, &dead, now); err != nil {
|
|
return err
|
|
}
|
|
if auditErr := enqueueKafkaAuditEvent(ctx, tenantID, "system", types.ZeroUid, "kafka.dead_letter.created", "kafka_event", dead.EventID, "dead", dead.LastReason, map[string]any{
|
|
"dead_id": dead.DeadID,
|
|
"event_type": dead.EventType,
|
|
"biz_type": dead.BizType,
|
|
"biz_id": dead.BizID,
|
|
"consumer_group": dead.ConsumerGroup,
|
|
}); auditErr != nil {
|
|
logs.Warn.Printf("kafka dead letter audit enqueue failed: event=%s dead=%s err=%v", dead.EventID, dead.DeadID, auditErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func tenantIDFromDeadLetter(envelope kafkaEnvelope, dead *types.KafkaDeadLetter) (types.TenantID, error) {
|
|
if !envelope.TenantID.IsZero() {
|
|
return envelope.TenantID, nil
|
|
}
|
|
if dead != nil && len(dead.Envelope) > 0 {
|
|
var original kafkaEnvelope
|
|
if err := json.Unmarshal(dead.Envelope, &original); err == nil && !original.TenantID.IsZero() {
|
|
return original.TenantID, nil
|
|
}
|
|
}
|
|
return kafkaTenantID(envelope.TenantCode)
|
|
}
|
|
|
|
func replayKafkaDeadLetter(ctx context.Context, tenantID types.TenantID, deadID string, actor types.Uid, note string) (*types.KafkaDeadLetter, error) {
|
|
now := time.Now().UTC().Round(time.Millisecond)
|
|
dead, err := store.HTTPInfrastructure.MarkKafkaDeadLetterReplay(tenantID, deadID, actor.UserId(), note, now)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var envelope kafkaEnvelope
|
|
if err = json.Unmarshal(dead.Envelope, &envelope); err != nil {
|
|
return nil, err
|
|
}
|
|
if envelope.EventID == "" {
|
|
envelope.EventID = dead.EventID
|
|
}
|
|
if envelope.ParentEventID == "" {
|
|
envelope.ParentEventID = dead.ParentEventID
|
|
}
|
|
if err = publishKafkaEnvelope(ctx, strings.TrimSpace(dead.SourceTopic), envelope.EventID, envelope); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = store.HTTPInfrastructure.MarkKafkaDeadLetter(tenantID, deadID, types.KafkaDeadStatusReplayed, actor.UserId(), note, now); err != nil {
|
|
return nil, err
|
|
}
|
|
return store.HTTPInfrastructure.GetKafkaDeadLetter(tenantID, deadID)
|
|
}
|
|
|
|
func resolveKafkaDeadLetter(tenantID types.TenantID, deadID, status string, actor types.Uid, note string) error {
|
|
now := time.Now().UTC().Round(time.Millisecond)
|
|
return store.HTTPInfrastructure.MarkKafkaDeadLetter(tenantID, deadID, status, actor.UserId(), note, now)
|
|
}
|