// Package service 定义 room-service 领域服务及其持久化边界。 package service import ( "context" "hyapp/services/room-service/internal/room/outbox" ) // RoomMeta 对应 rooms 表中房间的持久元数据。 type RoomMeta struct { // AppCode 是房间所属 App,和 RoomID 共同构成房间持久化隔离边界。 AppCode string // RoomID 是房间主键,也是恢复和 lease 路由的分区键。 RoomID string // RoomShortID 是客户端可搜索/展示的短房号,首版等于 owner 展示短号。 RoomShortID string // OwnerUserID 是房间所有者,恢复无快照房间时用于重建管理员集合。 OwnerUserID int64 // SeatCount 是初始麦位数量,只有无快照恢复时需要它重建麦位。 SeatCount int32 // Mode 是房间模式,当前作为元数据持久化。 Mode string // Status 是房间基础状态,快照缺失时用于恢复 RoomState.Status。 Status string // VisibleRegionID 是房间列表可见区域,0 表示 GLOBAL 兜底桶。 VisibleRegionID int64 } // CommandRecord 对应 room_command_log 中一条成功命令。 type CommandRecord struct { // AppCode 是命令所属 App,保证 command_id 幂等只在单个 App 内生效。 AppCode string // RoomID 是命令所属房间。 RoomID string // RoomVersion 是命令成功应用后的房间版本,恢复时按它排序回放。 RoomVersion int64 // CommandID 是幂等键,repository 必须保证同房间唯一。 CommandID string // CommandType 是反序列化命令 payload 的分发键。 CommandType string // Payload 是命令 JSON 序列化结果。 Payload []byte // Replayable 标记该命令是否参与恢复回放。 Replayable bool // OwnerNodeID 是提交命令时持有房间 lease 的 room-service 节点。 OwnerNodeID string // LeaseToken 是提交命令前通过 Redis fencing 校验的 lease token。 LeaseToken string // CreatedAtMS 是命令成功落盘的 UTC 毫秒时间戳,恢复和审计不依赖数据库时区。 CreatedAtMS int64 } // MutationCommit 表达一次 Room Cell 命令需要一起提交的持久化副作用。 type MutationCommit struct { // Command 是本次命令日志,所有成功命令都必须写入。 Command CommandRecord // OutboxRecords 是本次命令产生的房间外事件。 OutboxRecords []outbox.Record // RoomStatus 非空时同步更新 rooms 和 room_list_entries 的生命周期状态。 RoomStatus string // RoomSeatCount 非 nil 时同步更新 rooms 元数据中的麦位总数。 RoomSeatCount *int32 } // SnapshotRecord 对应 room_snapshots 中的一条最新快照。 type SnapshotRecord struct { // AppCode 是快照所属 App,恢复时必须和房间 meta 保持一致。 AppCode string // RoomID 是快照所属房间。 RoomID string // RoomVersion 是快照覆盖到的房间版本。 RoomVersion int64 // Payload 是 roomv1.RoomSnapshot 的 protobuf 序列化结果。 Payload []byte // CreatedAtMS 是快照生成的 UTC 毫秒时间戳。 CreatedAtMS int64 } // RoomListEntry 是 room_list_entries 的领域投影,只保存列表卡片所需字段。 type RoomListEntry struct { // AppCode 是列表投影所属 App,发现页不能跨 App 读到房间。 AppCode string // RoomID 是列表卡片和 JoinRoom 的跳转目标。 RoomID string // RoomShortID 是列表卡片展示/搜索用短房号。 RoomShortID string // VisibleRegionID 是列表隔离桶,查询只能按该字段过滤。 VisibleRegionID int64 // OwnerUserID 是房间所有者,用于卡片展示和运营排查。 OwnerUserID int64 // Title 是卡片标题;空值由客户端或后续资料聚合兜底。 Title string // CoverURL 是卡片封面;空值表示没有单独封面。 CoverURL string // Mode 是房间模式,来自 RoomState/RoomMeta。 Mode string // Status 是房间生命周期状态,列表默认只查 active。 Status string // Heat 是房间热度,当前主要由送礼累计。 Heat int64 // OnlineCount 是 room-service 业务 presence 数量。 OnlineCount int32 // SeatCount 是麦位总数。 SeatCount int32 // OccupiedSeatCount 是当前被占用麦位数。 OccupiedSeatCount int32 // SortScore 是热门列表排序分,算法不进入 RoomState。 SortScore int64 // CreatedAtMS 是创建房间时的毫秒时间,new tab 使用它排序。 CreatedAtMS int64 // UpdatedAtMS 是投影最后更新时间。 UpdatedAtMS int64 // FeedSubjectUserID 是关系房间流命中的好友/关注用户;只用于生成稳定 cursor,不出现在 HTTP 卡片里。 FeedSubjectUserID int64 } // RoomSeatConfig 是后台房间配置里控制创建/修改房间座位数的低频配置。 type RoomSeatConfig struct { // AppCode 是配置所属 App。 AppCode string // AllowedSeatCounts 是当前 App 允许客户端使用的麦位总数。 AllowedSeatCounts []int32 // DefaultSeatCount 是 CreateRoom 未传 seat_count 时使用的默认麦位总数。 DefaultSeatCount int32 // UpdatedAtMS 是配置最后更新时间,主要用于后台展示和排障。 UpdatedAtMS int64 } // RoomPresence 是当前用户可恢复房间的轻量读模型。 // 它从 RoomSnapshot 投影而来,只用于 App 启动/回前台探测,不替代 Room Cell 权威状态。 type RoomPresence struct { // AppCode 是 presence 所属 App,和 user_id 一起限定用户当前房间。 AppCode string // UserID 是业务 presence 所属用户。 UserID int64 // RoomID 是用户当前仍在的房间;status=left 时只保留排障参考。 RoomID string // Role 是房间内轻量角色,来自 RoomUser.role。 Role string // PublishState 是用户当前麦位发流状态,非上麦用户为空。 PublishState string // MicSessionID 是当前麦位发流会话,非上麦用户为空。 MicSessionID string // RoomVersion 是投影来自的房间版本。 RoomVersion int64 // Status 表示该用户 presence 是否仍 active。 Status string // LastSeenAtMS 是用户在 Room Cell 内的最后业务心跳时间。 LastSeenAtMS int64 // UpdatedAtMS 是投影写入时间。 UpdatedAtMS int64 } // RoomOnlineUserQuery 是房间在线用户分页读模型查询条件。 type RoomOnlineUserQuery struct { AppCode string RoomID string Page int PageSize int } // RoomOnlineUserPage 是 room_user_presence 中某房间 active 用户的一页结果。 type RoomOnlineUserPage struct { Users []RoomPresence Total int64 Page int PageSize int } // RoomListQuery 是 room-service 房间列表读模型查询条件。 type RoomListQuery struct { // AppCode 是列表查询所属 App,通常来自 gateway RequestMeta。 AppCode string // VisibleRegionID 是服务端解析出的用户区域,客户端不能伪造。 VisibleRegionID int64 // Tab 只允许 hot/new,决定公共发现列表排序字段。 Tab string // OwnerUserID 保留给内部定向读模型;公开 Mine 顶部卡片走 GetMyRoom。 OwnerUserID int64 // Query 非空时按 room_id 或 title 做包含匹配。 Query string // Limit 是实际查询数量,service 层会限制最大值。 Limit int // CursorSortScore 是 hot tab 的游标 score。 CursorSortScore int64 // CursorCreatedAtMS 是 new tab 的游标创建时间。 CursorCreatedAtMS int64 // CursorUpdatedAtMS 是用户关系 feed tab 的游标更新时间。 CursorUpdatedAtMS int64 // CursorRoomID 是同分或同时间下的稳定翻页边界。 CursorRoomID string } // RoomUserFeedEntry 是“访问过/好友/关注”房间流的读模型索引。 // 它只保存用户到房间的列表关系,不复制房间卡片字段,卡片内容仍来自 room_list_entries。 type RoomUserFeedEntry struct { // AppCode 是 feed 所属 App,防止跨 App 看到关系流。 AppCode string // UserID 是拥有该 feed 的用户。 UserID int64 // FeedType 是 visited/friend/following 之一;关系事实 owner 后续可通过事件填充 friend/following。 FeedType string // RoomID 是 feed 指向的房间。 RoomID string // CreatedAtMS 是首次建立该 feed 关系的 UTC 毫秒。 CreatedAtMS int64 // UpdatedAtMS 是 feed 排序时间,visited 会在 JoinRoom 成功后刷新。 UpdatedAtMS int64 } // RoomUserFeedQuery 是用户房间关系流的分页查询条件。 type RoomUserFeedQuery struct { // AppCode 是 feed 查询所属 App。 AppCode string // UserID 是当前登录用户。 UserID int64 // FeedType 是 visited/friend/following 之一。 FeedType string // VisibleRegionID 沿用房间发现页区域桶,避免关系流跨区域露出。 VisibleRegionID int64 // Query 非空时按 room_id、room_short_id 或 title 做包含匹配。 Query string // Limit 是实际查询数量,service 层会限制最大值。 Limit int // CursorUpdatedAtMS 是上一页最后一条 feed 更新时间。 CursorUpdatedAtMS int64 // CursorRoomID 是同时间下的稳定翻页边界。 CursorRoomID string } // RoomFeedRelatedUser 是 gateway 从 user-service 读取到的关系事实快照。 // room-service 只使用它做 active 房间卡片查询和排序,不保存好友/关注关系。 type RoomFeedRelatedUser struct { // UserID 是好友或关注对象的用户 ID。 UserID int64 // RelationUpdatedAtMS 是该关系的排序时间,followed_at_ms/friended_at_ms 都映射到这里。 RelationUpdatedAtMS int64 } // RoomRelatedFeedQuery 根据一批关系用户查询他们当前 active 房间卡片。 type RoomRelatedFeedQuery struct { // AppCode 是 feed 查询所属 App。 AppCode string // VisibleRegionID 沿用房间发现页区域桶,避免关系流跨区域露出。 VisibleRegionID int64 // RelatedUsers 是 user-service 已授权返回的好友/关注对象。 RelatedUsers []RoomFeedRelatedUser // Query 非空时按 room_id、room_short_id 或 title 做包含匹配。 Query string // Limit 是实际查询数量,service 层会限制最大值。 Limit int // CursorUpdatedAtMS 是上一页最后一条关系排序时间。 CursorUpdatedAtMS int64 // CursorSubjectUserID 是关系排序时间相同时的好友/关注用户边界。 CursorSubjectUserID int64 // CursorRoomID 是同时间下的稳定翻页边界。 CursorRoomID string } // Repository 聚合 room-service 在首版需要的全部持久化读写。 type Repository interface { // SaveRoomMeta 保存房间基础元数据,创建房间时必须先建立它。 SaveRoomMeta(ctx context.Context, meta RoomMeta) error // GetRoomMeta 读取房间基础元数据,恢复无内存 Cell 的房间依赖它。 GetRoomMeta(ctx context.Context, roomID string) (RoomMeta, bool, error) // GetRoomMetaByOwner 查询 owner 已创建的房间;产品规则要求一个用户只能创建一个房间。 GetRoomMetaByOwner(ctx context.Context, ownerUserID int64) (RoomMeta, bool, error) // GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。 GetCommand(ctx context.Context, roomID string, commandID string) (CommandRecord, bool, error) // SaveCommand 追加成功命令日志,关键命令恢复必须依赖它。 SaveCommand(ctx context.Context, record CommandRecord) error // SaveMutation 在同一事务中提交命令日志、outbox 事件和可选房间生命周期状态。 SaveMutation(ctx context.Context, commit MutationCommit) error // GetRoomSeatConfig 读取后台配置的房间座位数规则;不存在时 service 层使用内置默认值。 GetRoomSeatConfig(ctx context.Context) (RoomSeatConfig, bool, error) // UpsertRoomSeatConfig 写入房间座位数配置,供本地验证和后台配置共享同一张表。 UpsertRoomSeatConfig(ctx context.Context, config RoomSeatConfig) error // ListCommandsAfter 读取快照版本之后的命令,恢复时按版本顺序回放。 ListCommandsAfter(ctx context.Context, roomID string, roomVersion int64) ([]CommandRecord, error) // SaveSnapshot 保存最新房间快照,允许覆盖较低版本号快照但不能倒退。 SaveSnapshot(ctx context.Context, snapshot SnapshotRecord) error // GetLatestSnapshot 读取当前最新快照,恢复时优先使用它减少回放成本。 GetLatestSnapshot(ctx context.Context, roomID string) (SnapshotRecord, bool, error) // SaveOutbox 写入房间外事件,必须和成功命令保持同一提交语义。 SaveOutbox(ctx context.Context, records []outbox.Record) error // ListPendingOutbox 扫描待补偿投递事件。 ListPendingOutbox(ctx context.Context, limit int) ([]outbox.Record, error) // ClaimPendingOutbox 抢占一批待投递事件,避免多 worker 同时投递同一批。 ClaimPendingOutbox(ctx context.Context, workerID string, limit int, lockUntilMS int64) ([]outbox.Record, error) // MarkOutboxDelivered 标记事件已经投递成功。 MarkOutboxDelivered(ctx context.Context, eventID string) error // MarkOutboxRetryable 记录一次投递失败,并写入下一次允许抢占的退避时间。 MarkOutboxRetryable(ctx context.Context, eventID string, lastErr string, nextRetryAtMS int64) error // MarkOutboxDead 把超过最大重试次数的事件转入 failed,避免固定污染日志和外部依赖。 MarkOutboxDead(ctx context.Context, eventID string, lastErr string) error // UpsertRoomListEntry 写入或更新房间列表读模型;失败不应破坏 Room Cell 已提交状态。 UpsertRoomListEntry(ctx context.Context, entry RoomListEntry) error // ListRoomListEntries 按区域和 tab 查询房间列表,不访问 Room Cell 内存。 ListRoomListEntries(ctx context.Context, query RoomListQuery) ([]RoomListEntry, error) // UpsertRoomUserFeedEntry 写入用户房间关系流读模型;失败不应影响房间主链路。 UpsertRoomUserFeedEntry(ctx context.Context, entry RoomUserFeedEntry) error // ListRoomUserFeedEntries 按用户关系流读取房间卡片,不访问 Room Cell 内存集合。 ListRoomUserFeedEntries(ctx context.Context, query RoomUserFeedQuery) ([]RoomListEntry, error) // ListRoomRelatedFeedEntries 根据 user-service 关系事实查询 active 房间卡片,不持久化关系状态。 ListRoomRelatedFeedEntries(ctx context.Context, query RoomRelatedFeedQuery) ([]RoomListEntry, error) // ProjectRoomPresence 用最新快照刷新用户当前房间读模型;失败不应破坏 Room Cell 已提交状态。 ProjectRoomPresence(ctx context.Context, snapshot RoomPresenceSnapshot) error // GetCurrentRoomPresence 查询用户当前 active 房间 presence,不扫描房间列表或快照。 GetCurrentRoomPresence(ctx context.Context, userID int64) (RoomPresence, bool, error) // ListRoomOnlineUsers 查询房间 active 在线用户分页,不读取完整 RoomSnapshot。 ListRoomOnlineUsers(ctx context.Context, query RoomOnlineUserQuery) (RoomOnlineUserPage, error) } // RoomPresenceSnapshot 是 repository 投影接口的稳定入参,避免存储层反向依赖 protobuf。 type RoomPresenceSnapshot struct { AppCode string RoomID string RoomVersion int64 RoomStatus string Users []RoomPresence UpdatedAtMS int64 }