feat(Talk): 群操作写系统消息并广播会话事件
新增 AppendSystemMessage:改群名/群公告/移出成员/转让群主/设取消管理员时写入 MsgType=3 系统消息,同时回填会话最后消息摘要,使不在会话内的成员通过未读提醒感知(群公告提醒即走此路径)。 新增 BroadcastConversation,向会话广播 talk:conversation 事件(renamed/announcement/role_changed/members_removed/owner_transferred/dissolved),供前端立即同步会话列表与成员。
This commit is contained in:
1 parent
f608a6f9c0
commit
5707ab0371
1 file changed
+100
-1
@@ -4,7 +4,8 @@
|
||||
- 会话/成员/消息的增删改查与序列化
|
||||
- 4 项配额读取与用量统计(AppConfig 可配置,公司级 → 租户默认 → manifest 默认)
|
||||
- Talk 准入判定 CheckTalkAccess(供本 app 与 socket 模块共用)
|
||||
- 消息广播 BroadcastMessage / BroadcastRead(调用 gvsdsdk.talk_socket)
|
||||
- 消息广播 BroadcastMessage / BroadcastRead / BroadcastConversation
|
||||
(调用 gvsdsdk.talk_socket;后者用于群主/管理员操作后的会话级同步)
|
||||
|
||||
所有方法名遵循帕斯卡命名法;UUID 一律通过 UUIDObj(x).bytes 传递。
|
||||
"""
|
||||
@@ -204,6 +205,28 @@ def BroadcastRead(ConversationUUID, UserUUID, LastReadMessageUUID):
|
||||
logger.warning('Talk 已读广播失败', exc_info=True)
|
||||
|
||||
|
||||
def BroadcastConversation(ConversationUUID, Action, Extra=None):
|
||||
"""广播「会话级变更」事件(事件名 talk:conversation)
|
||||
|
||||
用于群主/管理员操作后让所有在线成员的会话列表立即同步:
|
||||
改名(renamed)、群公告(announcement)、转让群主(owner_transferred)、
|
||||
成员角色变更(role_changed)、移出成员(members_removed)、解散(dissolved)。
|
||||
事件名不带命名空间前缀,前端订阅的是 'talk:conversation'。
|
||||
"""
|
||||
try:
|
||||
from gvsdsdk.talk_socket import BroadcastToConversation
|
||||
except ImportError:
|
||||
logger.debug('gvsdsdk.talk_socket 不存在,跳过 Talk 会话事件广播')
|
||||
return
|
||||
data = {'ConversationUUID': _ToStr(ConversationUUID), 'Action': Action}
|
||||
if Extra:
|
||||
data.update(Extra)
|
||||
try:
|
||||
BroadcastToConversation(ConversationUUID, 'talk:conversation', data)
|
||||
except Exception:
|
||||
logger.warning('Talk 会话事件广播失败', exc_info=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 模块级便捷入口(gvsdsdk.talk_socket 按这些签名调用,勿改签名)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -565,6 +588,9 @@ class TalkManager:
|
||||
return False, '仅群主或管理员可修改群名', None
|
||||
conv.Title = (Title or '').strip()[:128]
|
||||
conv.save(update_fields=['Title'])
|
||||
TalkManager.AppendSystemMessage(
|
||||
conv, f'{TalkManager._DisplayName(operator)} 修改群名为「{conv.Title}」', operator)
|
||||
BroadcastConversation(conv.ConversationUUID, 'renamed', {'Title': conv.Title})
|
||||
return True, '', {'ConversationUUID': _ToStr(conv.ConversationUUID), 'Title': conv.Title}
|
||||
|
||||
@staticmethod
|
||||
@@ -615,6 +641,14 @@ class TalkManager:
|
||||
admin_count = TalkMember.objects.filter(
|
||||
ConversationUUID=conv.ConversationUUID, MemberRole=ROLE_ADMIN).count()
|
||||
name_map = TalkManager._UserNames({target})
|
||||
role_text = '管理员' if role == ROLE_ADMIN else '普通成员'
|
||||
TalkManager.AppendSystemMessage(
|
||||
conv,
|
||||
f'{TalkManager._DisplayName(operator)} 将 {name_map.get(target, "") or "某成员"} 设为{role_text}',
|
||||
operator,
|
||||
)
|
||||
BroadcastConversation(conv.ConversationUUID, 'role_changed',
|
||||
{'UserUUID': _ToStr(target), 'MemberRole': role})
|
||||
return True, '', {
|
||||
'ConversationUUID': _ToStr(conv.ConversationUUID),
|
||||
'UserUUID': _ToStr(target),
|
||||
@@ -664,7 +698,14 @@ class TalkManager:
|
||||
removed.append(_ToStr(uid))
|
||||
if not removed:
|
||||
return False, '没有可移出的成员', None
|
||||
removed_bytes = [_ToBytes(u) for u in removed]
|
||||
removed_names = TalkManager._UserNames(set(removed_bytes))
|
||||
names = '、'.join(removed_names.get(b, '') or '某成员' for b in removed_bytes)
|
||||
TalkManager.AppendSystemMessage(
|
||||
conv, f'{TalkManager._DisplayName(operator)} 将 {names} 移出群聊', operator)
|
||||
TalkManager._SyncMemberCount(conv)
|
||||
BroadcastConversation(conv.ConversationUUID, 'members_removed',
|
||||
{'Removed': removed, 'MemberCount': conv.MemberCount})
|
||||
if conv.MemberCount <= 0:
|
||||
conv.Status = STATUS_DISSOLVED
|
||||
conv.save(update_fields=['Status'])
|
||||
@@ -705,6 +746,13 @@ class TalkManager:
|
||||
operator_member.MemberRole = ROLE_ADMIN
|
||||
operator_member.save(update_fields=['MemberRole'])
|
||||
name_map = TalkManager._UserNames({target})
|
||||
TalkManager.AppendSystemMessage(
|
||||
conv,
|
||||
f'{TalkManager._DisplayName(operator)} 将群主转让给 {name_map.get(target, "") or "某成员"}',
|
||||
operator,
|
||||
)
|
||||
BroadcastConversation(conv.ConversationUUID, 'owner_transferred',
|
||||
{'OwnerUUID': _ToStr(target)})
|
||||
return True, '', {
|
||||
'ConversationUUID': _ToStr(conv.ConversationUUID),
|
||||
'OwnerUUID': _ToStr(target),
|
||||
@@ -729,6 +777,14 @@ class TalkManager:
|
||||
conv.Announcement = text[:2000]
|
||||
conv.AnnouncementTime = TalkManager._Now() if text else None
|
||||
conv.save(update_fields=['Announcement', 'AnnouncementTime'])
|
||||
operator_name = TalkManager._DisplayName(operator)
|
||||
TalkManager.AppendSystemMessage(
|
||||
conv,
|
||||
f'{operator_name} 更新了群公告' if conv.Announcement else f'{operator_name} 清除了群公告',
|
||||
operator,
|
||||
)
|
||||
BroadcastConversation(conv.ConversationUUID, 'announcement',
|
||||
{'Announcement': conv.Announcement})
|
||||
return True, '', {
|
||||
'ConversationUUID': _ToStr(conv.ConversationUUID),
|
||||
'Announcement': conv.Announcement,
|
||||
@@ -747,6 +803,7 @@ class TalkManager:
|
||||
return False, '仅群主或 Talk 管理员可解散该会话', None
|
||||
conv.Status = STATUS_DISSOLVED
|
||||
conv.save(update_fields=['Status'])
|
||||
BroadcastConversation(conv.ConversationUUID, 'dissolved')
|
||||
return True, '', {'ConversationUUID': _ToStr(conv.ConversationUUID)}
|
||||
|
||||
# ==================== 消息 ====================
|
||||
@@ -884,6 +941,48 @@ class TalkManager:
|
||||
|
||||
# ==================== 内部辅助 ====================
|
||||
|
||||
@staticmethod
|
||||
def AppendSystemMessage(Conversation, Text, OperatorUUID=None):
|
||||
"""写入一条系统消息(MsgType=3)并广播
|
||||
|
||||
群主/管理员操作留痕(改群名、群公告、移出成员、转让群主、设/取消管理员)。
|
||||
同时回填会话最后消息摘要,使不在会话内的成员通过未读提醒感知(群公告提醒即走此路径)。
|
||||
|
||||
Returns:
|
||||
dict|None:消息视图 dict(Text 为空时返回 None)
|
||||
"""
|
||||
text = (Text or '').strip()
|
||||
if not text:
|
||||
return None
|
||||
sender = _ToBytes(OperatorUUID) or _ToBytes(Conversation.CreatorUUID)
|
||||
if sender is None:
|
||||
return None
|
||||
now = TalkManager._Now()
|
||||
msg = TalkMessage.objects.create(
|
||||
MessageUUID=uuid.uuid4().bytes,
|
||||
ConversationUUID=Conversation.ConversationUUID,
|
||||
CompanyUUID=Conversation.CompanyUUID,
|
||||
SenderUUID=sender,
|
||||
MsgType=MSG_SYSTEM,
|
||||
Content=text,
|
||||
CreateTime=now,
|
||||
)
|
||||
Conversation.LastMessageUUID = msg.MessageUUID
|
||||
Conversation.LastMessageTime = now
|
||||
Conversation.LastMessagePreview = text[:256]
|
||||
Conversation.save(update_fields=[
|
||||
'LastMessageUUID', 'LastMessageTime', 'LastMessagePreview'])
|
||||
message_dict = TalkManager._MessageDict(msg, TalkManager._UserNames({sender}))
|
||||
BroadcastMessage(Conversation.ConversationUUID, message_dict)
|
||||
return message_dict
|
||||
|
||||
@staticmethod
|
||||
def _DisplayName(UserUUIDBytes):
|
||||
"""操作人显示名(取不到时回退「某成员」,保证系统消息可读)"""
|
||||
if not UserUUIDBytes:
|
||||
return '系统'
|
||||
return TalkManager._UserNames({UserUUIDBytes}).get(UserUUIDBytes, '') or '某成员'
|
||||
|
||||
@staticmethod
|
||||
def _Now():
|
||||
return TalkConversation._meta.get_field('CreateTime').default()
|
||||
|
||||
Reference in new issue
Block a user