88 lines
3.0 KiB
Go
88 lines
3.0 KiB
Go
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
|
||
}
|