feat: complete IM P0 and P1 reliability work
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tinode/chat/server/logs"
|
||||
"github.com/tinode/chat/server/store"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
func stableIMEventID(parts ...any) string {
|
||||
return types.StableEventID(parts...)
|
||||
}
|
||||
|
||||
func enqueueIMBusinessEvent(tenantID types.TenantID, eventID, targetType, target string,
|
||||
actor types.Uid, eventType string, payload any) error {
|
||||
if tenantID.IsZero() || eventID == "" || target == "" || eventType == "" {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = store.HTTPInfrastructure.CreateEvent(tenantID, &types.BusinessEvent{
|
||||
EventID: eventID, TargetType: targetType, Target: target, ActorUserID: actor,
|
||||
EventType: eventType, Payload: encoded,
|
||||
})
|
||||
if err == nil {
|
||||
dispatchIMBusinessEvents(tenantID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func createIMSystemNotification(tenantID types.TenantID, uid types.Uid, eventID, eventType,
|
||||
title, summary string, action types.KVMap) error {
|
||||
if uid.IsZero() {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
return store.Security.CreateSystemNotification(tenantID, &types.SystemNotification{
|
||||
TenantID: tenantID, UserID: uid, EventID: eventID, EventType: eventType,
|
||||
Title: title, Summary: summary, Action: action,
|
||||
})
|
||||
}
|
||||
|
||||
func publishIMMeetingEvent(tenantID types.TenantID, actor types.Uid, meeting *types.Meeting, eventType string) error {
|
||||
if meeting == nil {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
for _, participant := range meeting.Participants {
|
||||
uid := participant.UserID
|
||||
if uid.IsZero() {
|
||||
continue
|
||||
}
|
||||
eventID := stableIMEventID("meeting", meeting.MeetingID, meeting.Version, eventType, uid.UserId())
|
||||
if uid != actor {
|
||||
if err := createIMSystemNotification(tenantID, uid, stableIMEventID(eventID, "notification"), eventType,
|
||||
"会议通知", meeting.Title, types.KVMap{"meeting_id": meeting.MeetingID}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := enqueueIMBusinessEvent(tenantID, eventID, "user", uid.UserId(), actor, eventType,
|
||||
map[string]any{"meeting_id": meeting.MeetingID, "version": meeting.Version, "state": meeting.State}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishIMRelationEvent(tenantID types.TenantID, actor, target types.Uid, stableID, eventType, title, summary string) error {
|
||||
if target.IsZero() {
|
||||
return types.ErrMalformed
|
||||
}
|
||||
eventID := stableIMEventID("relation", stableID, eventType, target.UserId())
|
||||
if target != actor && title != "" {
|
||||
if err := createIMSystemNotification(tenantID, target, stableIMEventID(eventID, "notification"), eventType,
|
||||
title, summary, types.KVMap{"user_id": actor.UserId()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return enqueueIMBusinessEvent(tenantID, eventID, "user", target.UserId(), actor, eventType,
|
||||
map[string]any{"user_id": actor.UserId()})
|
||||
}
|
||||
|
||||
func dispatchIMBusinessEvents(tenantID types.TenantID) {
|
||||
if tenantID.IsZero() || globals.hub == nil || store.HTTPInfrastructure == nil {
|
||||
return
|
||||
}
|
||||
now := time.Now().UTC().Round(time.Millisecond)
|
||||
events, err := store.HTTPInfrastructure.ListPendingEvents(tenantID, 100, now)
|
||||
if err != nil {
|
||||
logs.Warn.Printf("business outbox: list tenant=%d: %v", tenantID, err)
|
||||
return
|
||||
}
|
||||
for _, event := range events {
|
||||
from := ""
|
||||
if !event.ActorUserID.IsZero() {
|
||||
from = event.ActorUserID.UserId()
|
||||
}
|
||||
payload := json.RawMessage(event.Payload)
|
||||
if len(payload) == 0 {
|
||||
payload = json.RawMessage(`{}`)
|
||||
}
|
||||
message := &ServerComMessage{
|
||||
TenantID: tenantID, RcptTo: event.Target, AsUser: from, Timestamp: event.CreatedAt,
|
||||
Info: &MsgServerInfo{Topic: event.Target, From: from, What: "im", Event: event.EventType, Payload: payload},
|
||||
}
|
||||
select {
|
||||
case globals.hub.routeSrv <- message:
|
||||
if err = store.HTTPInfrastructure.MarkEventDelivered(tenantID, event.EventID, now); err != nil {
|
||||
logs.Warn.Printf("business outbox: mark delivered event=%s: %v", event.EventID, err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
logs.Warn.Printf("business outbox: hub queue full event=%s target=%s", event.EventID, strings.TrimSpace(event.Target))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user