fix group invitation notifications
This commit is contained in:
+2
-2
@@ -328,7 +328,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key>
|
|||||||
|
|
||||||
##### 0.5.4.3.1 会话内系统提示和可点击动作消息
|
##### 0.5.4.3.1 会话内系统提示和可点击动作消息
|
||||||
|
|
||||||
截图中的灰色提示、入群邀请说明、好友已添加提示等属于会话内系统消息,必须进入对应 P2P 或群 Topic 的消息流,占用 `seq`,参与历史拉取、重连补偿和多端同步。它们不是 `info` 实时业务事件,也不是系统通知列表记录;`info` 只负责触发刷新,真正需要在聊天窗口长期展示的内容必须以 `{data}` 下发并入库。
|
截图中的灰色提示、群内邀请确认说明、好友已添加提示等属于会话内系统消息,必须进入对应 P2P 或群 Topic 的消息流,占用 `seq`,参与历史拉取、重连补偿和多端同步。它们不是 `info` 实时业务事件,也不是系统通知列表记录;`info` 只负责触发刷新,真正需要在聊天窗口长期展示的内容必须以 `{data}` 下发并入库。发给被邀请人的“加入群聊”邀请卡片不写入邀请人与被邀请人的 P2P 会话,展示以 `GET /v1/im/system-notifications` 返回的系统通知记录为准。
|
||||||
|
|
||||||
客户端不得主动发送 `system_notice`、`system_action`;这两类消息只能由服务端受控身份生成。服务端下发时 `data.from` 可以为空,客户端以 `head.x-im-from="system"` 判断系统身份,不依赖真实用户头像。客户端收到未知 `template` 或未知 `action.type` 时,展示 `content.text`,忽略不可识别动作,不允许执行任意 URL、脚本或非白名单路由。
|
客户端不得主动发送 `system_notice`、`system_action`;这两类消息只能由服务端受控身份生成。服务端下发时 `data.from` 可以为空,客户端以 `head.x-im-from="system"` 判断系统身份,不依赖真实用户头像。客户端收到未知 `template` 或未知 `action.type` 时,展示 `content.text`,忽略不可识别动作,不允许执行任意 URL、脚本或非白名单路由。
|
||||||
|
|
||||||
@@ -1607,7 +1607,7 @@ X-MeetingIM-APIKey: <预置的普通 API Key>
|
|||||||
|
|
||||||
**状态:新增邀请卡片和加入接口。**
|
**状态:新增邀请卡片和加入接口。**
|
||||||
|
|
||||||
- 邀请卡展示以群邀请接口返回和系统通知记录为准;相关实时刷新使用 `info.what="group"` 群业务事件。
|
- 邀请卡展示以群邀请接口返回和系统通知记录为准;系统通知 `event_type=group.invitation.created`,`action.type=group_invitation_accept`,`action.invitation_id` 用于点击加入,`action.topic` 为目标群。相关实时刷新使用 `info.what="group"` 群业务事件。
|
||||||
- 点击加入:`POST /v1/im/group-invitations/{invitation_id}/accept`,带幂等键;返回 `status=joined|pending_approval` 和 `topic`。
|
- 点击加入:`POST /v1/im/group-invitations/{invitation_id}/accept`,带幂等键;返回 `status=joined|pending_approval` 和 `topic`。
|
||||||
- 服务端校验邀请租户、有效期、群状态、成员资格和成员数上限;重复加入返回当前状态,过期返回 `INVITATION_EXPIRED`。
|
- 服务端校验邀请租户、有效期、群状态、成员资格和成员数上限;重复加入返回当前状态,过期返回 `INVITATION_EXPIRED`。
|
||||||
|
|
||||||
|
|||||||
+21
-7
@@ -5381,13 +5381,7 @@ func serveIMGroupInvitations(wrt http.ResponseWriter, req *http.Request, topic s
|
|||||||
if inv.JoinRequestID != "" {
|
if inv.JoinRequestID != "" {
|
||||||
item["join_request_id"] = inv.JoinRequestID
|
item["join_request_id"] = inv.JoinRequestID
|
||||||
}
|
}
|
||||||
if imReliableHTTPEventsEnabled(req) {
|
publishIMGroupInvitationNotification(principal.TenantID, inv)
|
||||||
publishIMConversationSystemNotice(principal.TenantID, principal.Uid.P2PName(uid),
|
|
||||||
"system_action", "group_invitation_created", "邀请你加入群聊。",
|
|
||||||
[]imSystemMessageActor{{UserID: principal.Uid.UserId(), Click: map[string]any{"type": "user_profile", "target_id": principal.Uid.UserId()}}},
|
|
||||||
[]imSystemMessageAction{{Type: "group_invitation_accept", Label: "立即加入", TargetID: inv.InvitationID}},
|
|
||||||
map[string]any{"invitation_id": inv.InvitationID, "topic": topic, "message": inv.Message})
|
|
||||||
}
|
|
||||||
results = append(results, item)
|
results = append(results, item)
|
||||||
}
|
}
|
||||||
dispatchIMGroupEvents(principal.TenantID)
|
dispatchIMGroupEvents(principal.TenantID)
|
||||||
@@ -11032,6 +11026,26 @@ func broadcastIMConversationSystemMessage(tenantID types.TenantID, msg *types.Me
|
|||||||
broadcastHTTPForwardPresence(tenantID, msg, timestamp)
|
broadcastHTTPForwardPresence(tenantID, msg, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func publishIMGroupInvitationNotification(tenantID types.TenantID, inv *types.GroupInvitation) {
|
||||||
|
if tenantID == types.ZeroTenantID || inv == nil || inv.TargetUserID.IsZero() || store.Security == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
eventID := stableIMEventID("group-invitation", inv.InvitationID, inv.TargetUserID.UserId(), "created")
|
||||||
|
action := types.KVMap{
|
||||||
|
"type": "group_invitation_accept",
|
||||||
|
"invitation_id": inv.InvitationID,
|
||||||
|
"topic": inv.Topic,
|
||||||
|
"inviter_id": inv.InviterUserID.UserId(),
|
||||||
|
}
|
||||||
|
if inv.Message != "" {
|
||||||
|
action["message"] = inv.Message
|
||||||
|
}
|
||||||
|
if err := createIMSystemNotification(tenantID, inv.TargetUserID, eventID, "group.invitation.created",
|
||||||
|
"群聊邀请", "邀请你加入群聊。", action); err != nil {
|
||||||
|
logs.Warn.Printf("group invitation: create notification invitation=%s target=%s: %v", inv.InvitationID, inv.TargetUserID.UserId(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func deliverIMConversationSystemMessageToP2PSessions(tenantID types.TenantID, msg *types.Message, out *ServerComMessage) {
|
func deliverIMConversationSystemMessageToP2PSessions(tenantID types.TenantID, msg *types.Message, out *ServerComMessage) {
|
||||||
if globals.sessionStore == nil || msg == nil || out == nil || out.Data == nil {
|
if globals.sessionStore == nil || msg == nil || out == nil || out.Data == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
+19
-1
@@ -312,6 +312,7 @@ type testMailboxStore struct {
|
|||||||
|
|
||||||
type testUserSecurityStore struct {
|
type testUserSecurityStore struct {
|
||||||
unreadByPrefix map[string]int
|
unreadByPrefix map[string]int
|
||||||
|
notifications []types.SystemNotification
|
||||||
}
|
}
|
||||||
|
|
||||||
type testProfilesStore struct {
|
type testProfilesStore struct {
|
||||||
@@ -1003,7 +1004,10 @@ func (s *testUserSecurityStore) SetYouthMode(_ types.TenantID, uid types.Uid, en
|
|||||||
return &types.YouthModeSettings{UserID: uid, Enabled: enabled, PolicyVersion: policyVersion, UpdatedAt: now}, nil
|
return &types.YouthModeSettings{UserID: uid, Enabled: enabled, PolicyVersion: policyVersion, UpdatedAt: now}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testUserSecurityStore) CreateSystemNotification(_ types.TenantID, _ *types.SystemNotification) error {
|
func (s *testUserSecurityStore) CreateSystemNotification(_ types.TenantID, notification *types.SystemNotification) error {
|
||||||
|
if notification != nil {
|
||||||
|
s.notifications = append(s.notifications, *notification)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4704,13 +4708,17 @@ func TestServeIMGroupInvitationAddsNonMember(t *testing.T) {
|
|||||||
mockStore.EXPECT().GetUidString().Return("test")
|
mockStore.EXPECT().GetUidString().Return("test")
|
||||||
oldSubs := store.Subs
|
oldSubs := store.Subs
|
||||||
oldGroupBusiness := store.GroupBusiness
|
oldGroupBusiness := store.GroupBusiness
|
||||||
|
oldSecurity := store.Security
|
||||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||||
groupStore := &testGroupBusinessStore{}
|
groupStore := &testGroupBusinessStore{}
|
||||||
|
securityStore := &testUserSecurityStore{}
|
||||||
store.Subs = subs
|
store.Subs = subs
|
||||||
store.GroupBusiness = groupStore
|
store.GroupBusiness = groupStore
|
||||||
|
store.Security = securityStore
|
||||||
defer func() {
|
defer func() {
|
||||||
store.Subs = oldSubs
|
store.Subs = oldSubs
|
||||||
store.GroupBusiness = oldGroupBusiness
|
store.GroupBusiness = oldGroupBusiness
|
||||||
|
store.Security = oldSecurity
|
||||||
}()
|
}()
|
||||||
owner := types.Uid(42)
|
owner := types.Uid(42)
|
||||||
target := types.Uid(43)
|
target := types.Uid(43)
|
||||||
@@ -4737,6 +4745,16 @@ func TestServeIMGroupInvitationAddsNonMember(t *testing.T) {
|
|||||||
if len(groupStore.invitations) != 1 || groupStore.invitations[0].TargetUserID != target || groupStore.invitations[0].InvitationID != "ginv_test" {
|
if len(groupStore.invitations) != 1 || groupStore.invitations[0].TargetUserID != target || groupStore.invitations[0].InvitationID != "ginv_test" {
|
||||||
t.Fatalf("invitation was not persisted: %#v", groupStore.invitations)
|
t.Fatalf("invitation was not persisted: %#v", groupStore.invitations)
|
||||||
}
|
}
|
||||||
|
if len(securityStore.notifications) != 1 {
|
||||||
|
t.Fatalf("expected one target notification, got %#v", securityStore.notifications)
|
||||||
|
}
|
||||||
|
notification := securityStore.notifications[0]
|
||||||
|
if notification.UserID != target || notification.EventType != "group.invitation.created" {
|
||||||
|
t.Fatalf("unexpected notification target/type: %#v", notification)
|
||||||
|
}
|
||||||
|
if notification.Action["type"] != "group_invitation_accept" || notification.Action["invitation_id"] != "ginv_test" || notification.Action["topic"] != "grpAlpha" {
|
||||||
|
t.Fatalf("unexpected notification action: %#v", notification.Action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
|
func TestServeIMGroupInvitationCreatesApprovalRequest(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user