2026-05-14 00:25:02 +08:00

88 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"google.golang.org/protobuf/proto"
roomv1 "hyapp.local/api/proto/room/v1"
"hyapp/pkg/appcode"
"hyapp/services/room-service/internal/room/cell"
"hyapp/services/room-service/internal/room/rank"
)
// currentSnapshot 返回当前房间的最新快照;没有内存 Cell 时会从持久化恢复后再返回。
func (s *Service) currentSnapshot(ctx context.Context, roomID string) (*roomv1.RoomSnapshot, error) {
if roomCell := s.loadCell(ctx, roomID); roomCell != nil {
// 内存 Cell 是最新状态来源guard 查询优先读它。
currentState, _, err := roomCell.Snapshot(ctx)
if err != nil {
return nil, err
}
return snapshotWithApp(ctx, currentState.ToProto()), nil
}
// 没有内存 Cell 时从 meta + snapshot + command log 恢复,避免守卫读到落后于 command log 的旧快照。
roomMeta, exists, err := s.repository.GetRoomMeta(ctx, roomID)
if err != nil || !exists {
return nil, err
}
restoredState, err := s.recoverRoom(ctx, roomMeta)
if err != nil {
// 恢复失败说明持久化状态不可用,不能让外部 IM 入口自行放行。
return nil, err
}
if s.isDraining() {
// 下线期间守卫查询可以读取恢复后的快照,但不能再创建新的本地 Room Cell。
return snapshotWithApp(ctx, restoredState.ToProto()), nil
}
roomCell := cell.New(restoredState, rank.FromState(restoredState.GiftRank, s.rankLimit))
s.installCell(ctx, roomID, roomCell)
return snapshotWithApp(ctx, restoredState.ToProto()), nil
}
// persistSnapshot 持久化 protobuf 快照,作为后续恢复和 guard 查询的快速入口。
func (s *Service) persistSnapshot(ctx context.Context, snapshot *roomv1.RoomSnapshot) error {
snapshot = snapshotWithApp(ctx, snapshot)
// 快照统一使用 protobuf保持和跨服务契约一致。
payload, err := proto.Marshal(snapshot)
if err != nil {
return err
}
return s.repository.SaveSnapshot(ctx, SnapshotRecord{
AppCode: snapshot.GetAppCode(),
RoomID: snapshot.GetRoomId(),
RoomVersion: snapshot.GetVersion(),
Payload: payload,
CreatedAtMS: s.clock.Now().UnixMilli(),
})
}
func snapshotWithApp(ctx context.Context, snapshot *roomv1.RoomSnapshot) *roomv1.RoomSnapshot {
if snapshot == nil {
return nil
}
// RoomState 本身不保存租户上下文,快照出服务边界前统一补 app_code。
snapshot.AppCode = appcode.FromContext(ctx)
if snapshot.RoomShortId == "" && snapshot.GetRoomExt() != nil {
// room_short_id 已提升为顶层字段,但旧快照/恢复路径仍可能只存在于 RoomExt。
snapshot.RoomShortId = snapshot.GetRoomExt()["room_short_id"]
}
return snapshot
}
// shouldSnapshot 判定当前命令结果是否需要落快照。
func shouldSnapshot(snapshot *roomv1.RoomSnapshot, every int64) bool {
if snapshot == nil || every <= 1 {
// every<=1 表示每次有快照结果都保存nil 快照永远不保存。
return snapshot != nil
}
// 按房间版本取模做简单采样,避免引入额外计数状态。
return snapshot.GetVersion()%every == 0
}