99 lines
4.3 KiB
Go
99 lines
4.3 KiB
Go
package rpc
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/gotd/td/tg"
|
||
"go.uber.org/zap"
|
||
"telesrv/internal/domain"
|
||
)
|
||
|
||
// advanceForumGeneralReadAfterChannelRead 在「直接的频道级 readHistory」(channels.readHistory /
|
||
// messages.readHistory 频道 peer) 推进频道级已读后,顺带把 forum 的 General(topic 1) 话题级水位
|
||
// 也推进到同一个 maxID,并向自己其它设备/当前设备下发 updateReadChannelDiscussionInbox、向
|
||
// General 内发送者下发已读回执。
|
||
//
|
||
// 语义:General 消息即频道根历史(reply_to_top_id ∈ {0,1}),被频道级已读覆盖,故频道级读到顶
|
||
// ⇒ General 也读到顶。仅对 forum 生效(read.Forum 守卫,避免对普通频道多一次查询)。**普通子话题
|
||
// 的 readDiscussion 不经此路径**(其内部对频道级 ReadHistory 的「保守叠加」由 onMessagesReadDiscussion
|
||
// 直接调 service,不触达本 RPC handler),因此读子话题不会回灌 General —— 零话题间 cross-talk,
|
||
// 只有真正的频道级整读才推进 General。
|
||
func (r *Router) advanceForumGeneralReadAfterChannelRead(ctx context.Context, userID int64, read domain.ReadChannelHistoryResult) {
|
||
if r.deps.Channels == nil || !read.Forum || read.ChannelID == 0 || read.MaxID <= 0 {
|
||
return
|
||
}
|
||
topicRes, err := r.deps.Channels.ReadTopicHistory(ctx, userID, domain.ReadChannelTopicHistoryRequest{
|
||
UserID: userID,
|
||
ChannelID: read.ChannelID,
|
||
TopicID: domain.ForumGeneralTopicID,
|
||
MaxID: read.MaxID,
|
||
Date: int(r.clock.Now().Unix()),
|
||
})
|
||
if err != nil || !topicRes.Changed {
|
||
return
|
||
}
|
||
// 话题级水位此刻已落库;下面的 durable 事件+在线推送失败只影响其它设备的即时刷新,
|
||
// getForumTopics 重拉会收敛,故失败仅记日志、不阻断已成功的频道级 readHistory。
|
||
if err := r.recordChannelDiscussionInbox(ctx, userID, read.ChannelID, topicRes.TopicID, topicRes.MaxID, topicRes.Pts); err != nil {
|
||
r.log.Warn("record general discussion inbox after channel read",
|
||
zap.Int64("channel_id", read.ChannelID), zap.Error(err))
|
||
return
|
||
}
|
||
r.pushChannelDiscussionOutboxUpdates(ctx, read.ChannelID, topicRes.TopicID, topicRes.OutboxUpdates)
|
||
}
|
||
|
||
// recordChannelDiscussionInbox 记录 forum 话题级已读并向自己其它设备/当前设备下发
|
||
// updateReadChannelDiscussionInbox(durable + 账号 pts 簿记,供离线差分恢复)。
|
||
func (r *Router) recordChannelDiscussionInbox(ctx context.Context, userID, channelID int64, topicID, maxID, channelPts int) error {
|
||
if maxID <= 0 || channelID == 0 || topicID <= 0 {
|
||
return nil
|
||
}
|
||
event := domain.UpdateEvent{
|
||
Type: domain.UpdateEventReadChannelDiscussionInbox,
|
||
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: channelID},
|
||
TopMsgID: topicID,
|
||
MaxID: maxID,
|
||
ChannelPts: channelPts,
|
||
Date: int(r.clock.Now().Unix()),
|
||
PtsCount: 1,
|
||
}
|
||
recorded := event
|
||
if r.deps.Updates != nil {
|
||
authKeyID, _ := AuthKeyIDFrom(ctx)
|
||
sessionID, _ := SessionIDFrom(ctx)
|
||
rec, _, err := r.deps.Updates.RecordChannelDiscussionInbox(ctx, authKeyID, userID, channelID, topicID, maxID, sessionID)
|
||
if err != nil {
|
||
return internalErr()
|
||
}
|
||
recorded = rec
|
||
}
|
||
r.pushCurrentReadHistoryEvent(ctx, recorded)
|
||
r.pushReadHistoryEvent(ctx, userID, recorded)
|
||
return nil
|
||
}
|
||
|
||
// pushChannelDiscussionOutboxUpdates 在线向话题内发送者下发已读回执
|
||
// updateReadChannelDiscussionOutbox。与频道级 pushChannelReadOutboxUpdates 同为在线 best-effort
|
||
// (不进 durable,客户端重连可由 getForumTopics 的 read_outbox_max_id 兜底)。
|
||
func (r *Router) pushChannelDiscussionOutboxUpdates(ctx context.Context, channelID int64, topicID int, updates []domain.ChannelReadOutboxUpdate) {
|
||
if r.deps.Sessions == nil || channelID == 0 || topicID <= 0 || len(updates) == 0 {
|
||
return
|
||
}
|
||
seen := make(map[int64]int, len(updates))
|
||
for _, update := range updates {
|
||
if update.UserID == 0 || update.MaxID <= 0 {
|
||
continue
|
||
}
|
||
if seen[update.UserID] < update.MaxID {
|
||
seen[update.UserID] = update.MaxID
|
||
}
|
||
}
|
||
date := int(r.clock.Now().Unix())
|
||
for userID, maxID := range seen {
|
||
r.pushUserUpdates(ctx, userID, &tg.Updates{
|
||
Updates: []tg.UpdateClass{&tg.UpdateReadChannelDiscussionOutbox{ChannelID: channelID, TopMsgID: topicID, ReadMaxID: maxID}},
|
||
Date: date,
|
||
Seq: 0,
|
||
})
|
||
}
|
||
}
|