updateChannelParticipant carries the account's qts per the MTProto spec, but the server always sent Qts: 0, so real clients silently discarded it as a stale duplicate -- the banned/kicked user's channel never vanished locally and no correct "removed by admin" message showed, even though the update was delivered successfully at the transport layer. Add a durable per-device qts queue (channel_participant_event_queue) sharing its qts number space with the existing secret-chat queue (one qts sequence per device, per spec), and use it to stamp a correct, monotonically increasing qts on the update for every device of the affected user -- for channel bans/kicks, admin promotion/demotion, and ownership transfer. A device offline when it happened can now recover the event via updates.getDifference instead of missing it permanently.
78 lines
5.7 KiB
Go
78 lines
5.7 KiB
Go
package store
|
||
|
||
import (
|
||
"context"
|
||
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
// SecretChatStore 持久化私聊密聊握手状态机真值(memory/postgres 双实现,行为契约由
|
||
// storetest 钉死)。服务端是盲中继:g_a/g_b/key_fingerprint 原样不透明存储。
|
||
// 设计见 docs/secret-chat-module.md。
|
||
type SecretChatStore interface {
|
||
// CreateSecretChat 插入 requested 态密聊。chat_id 必须等于 requestEncryption.random_id;
|
||
// 主键/幂等键撞键返回 domain.ErrSecretChatRandomIDDuplicate,禁止另分配 ID。
|
||
CreateSecretChat(ctx context.Context, chat domain.SecretChat) error
|
||
// GetSecretChat 按 chat_id 取密聊。
|
||
GetSecretChat(ctx context.Context, chatID int) (domain.SecretChat, bool, error)
|
||
// AcceptSecretChat 原子 CAS 绑定:仅当 requested 且 participant_auth_key_id 未绑定时
|
||
// 迁移到 normal、落 g_b/key_fingerprint、绑定接受设备。并发第二个 accept 或已成型/
|
||
// 已销毁分别返回 domain.ErrSecretChatAlreadyAccepted / ErrSecretChatAlreadyDeclined;
|
||
// chat_id 不存在返回 domain.ErrSecretChatNotFound。
|
||
AcceptSecretChat(ctx context.Context, chatID int, participantAuthKeyID int64, gb []byte, keyFingerprint int64) (domain.SecretChat, error)
|
||
// DiscardSecretChat 迁移到 discarded(幂等);已是终态时 already=true 返回当前快照;
|
||
// chat_id 不存在返回 domain.ErrSecretChatNotFound。
|
||
DiscardSecretChat(ctx context.Context, chatID int, historyDeleted bool) (chat domain.SecretChat, already bool, err error)
|
||
// ListActiveSecretChatsByAuthKey 返回绑定该设备 perm auth_key(作为发起方 admin 或
|
||
// 接受方 participant)且未终态的密聊,按 chat_id 升序。设备登出 / 授权撤销时用于级联
|
||
// discard 并通知对端(避免对端继续往死 auth_key 投递的静默死链)。authKeyID==0 返回 nil。
|
||
ListActiveSecretChatsByAuthKey(ctx context.Context, authKeyID int64) ([]domain.SecretChat, error)
|
||
}
|
||
|
||
// EncryptedQueueStore 持久化密聊 qts 投递队列(设备级,memory/postgres 双实现)。
|
||
// updateNewEncryptedMessage 按接收设备 qts 序列投递;qts 分配 + 写队列 + 推进
|
||
// reserved 水位在单事务内完成(无空洞)。盲中继:bytes 原样存储。
|
||
// 设计见 docs/secret-chat-module.md §7。
|
||
type EncryptedQueueStore interface {
|
||
// AppendEncryptedMessage 为接收设备分配下一个 qts(首值 1)并写入队列,与 reserved_qts
|
||
// 推进同事务。幂等:同 (receiver_auth_key_id, chat_id, random_id) 已存在时返回既有行 +
|
||
// existing=true(不重分配 qts,发送方丢响应重发拿同一 qts/date)。
|
||
AppendEncryptedMessage(ctx context.Context, msg domain.SecretChatMessage) (stored domain.SecretChatMessage, existing bool, err error)
|
||
// ListEncryptedMessagesSince 返回接收设备 qts > sinceQts 的连续消息(qts 升序,最多 limit)。
|
||
ListEncryptedMessagesSince(ctx context.Context, receiverAuthKeyID int64, sinceQts, limit int) ([]domain.SecretChatMessage, error)
|
||
// ReservedQts 返回接收设备当前已分配的最高 qts(getState 用,无行返 0)。
|
||
ReservedQts(ctx context.Context, receiverAuthKeyID int64) (int, error)
|
||
// AckEncryptedMessages 推进设备 confirmed_qts 到 maxQts(GREATEST 幂等,回退忽略),
|
||
// 标记 qts<=maxQts 行为 acked(receivedQueue)。
|
||
AckEncryptedMessages(ctx context.Context, receiverAuthKeyID int64, maxQts int) error
|
||
|
||
// AppendStateEvent 写入无 qts 的 durable 密聊状态事件(握手/已读,离线补偿)。
|
||
AppendStateEvent(ctx context.Context, ev domain.EncryptedStateEvent) (int64, error)
|
||
// ListUndeliveredStateEvents 返回某设备未投递的状态事件(账号级 target_auth_key_id=0
|
||
// 或绑定本设备),按 id 升序,最多 limit。
|
||
ListUndeliveredStateEvents(ctx context.Context, targetUserID, deviceAuthKeyID int64, limit int) ([]domain.EncryptedStateEvent, error)
|
||
// MarkStateEventsDelivered 登记某设备已投递这些事件(幂等)。
|
||
MarkStateEventsDelivered(ctx context.Context, deviceAuthKeyID int64, eventIDs []int64) error
|
||
|
||
// PutEncryptedFile 持久化密聊文件元数据快照(铸造后写一次,幂等覆盖)。
|
||
PutEncryptedFile(ctx context.Context, ownerUserID int64, ref domain.EncryptedFileRef) error
|
||
// GetEncryptedFile 按 id + access_hash 回查文件快照(inputEncryptedFile 复用路径)。
|
||
GetEncryptedFile(ctx context.Context, id, accessHash int64) (domain.EncryptedFileRef, bool, error)
|
||
}
|
||
|
||
// ChannelParticipantQueueStore 持久化 channel 成员关系自通知(kick/ban/promote/
|
||
// transfer)的设备级 qts 投递队列。updateChannelParticipant 走 qts 序列(MTProto
|
||
// 规范),与 EncryptedQueueStore 共用同一张 secret_qts_watermarks 水位表——每设备
|
||
// 只有一条 qts 序列,两类事件按 qts 交错,通过各自独立的表分别落盘,读侧
|
||
// (updates.getDifference)按 qts 归并成连续序列。
|
||
type ChannelParticipantQueueStore interface {
|
||
// AppendChannelParticipantEvent 为接收设备分配下一个 qts 并写入队列,与
|
||
// reserved_qts 推进同事务(与 AppendEncryptedMessage 共用同一水位表)。
|
||
AppendChannelParticipantEvent(ctx context.Context, ev domain.DeviceChannelParticipantEvent) (domain.DeviceChannelParticipantEvent, error)
|
||
// ListChannelParticipantEventsSince 返回接收设备 qts > sinceQts 的连续事件
|
||
// (qts 升序,最多 limit)。
|
||
ListChannelParticipantEventsSince(ctx context.Context, receiverAuthKeyID int64, sinceQts, limit int) ([]domain.DeviceChannelParticipantEvent, error)
|
||
// AckChannelParticipantEvents 标记 qts<=maxQts 行为 acked(confirmed_qts 由
|
||
// AckEncryptedMessages 在同一张水位表上推进,这里只做本表的 GC 标记)。
|
||
AckChannelParticipantEvents(ctx context.Context, receiverAuthKeyID int64, maxQts int) error
|
||
}
|