656 lines
27 KiB
Go
656 lines
27 KiB
Go
// Package command 定义 room-service 可持久化、可回放的房间命令模型。
|
||
package command
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
)
|
||
|
||
// Command 统一表达可写入 command log 的房间命令。
|
||
type Command interface {
|
||
// ID 返回命令幂等键,通常来自 gateway 的 RequestMeta.command_id。
|
||
ID() string
|
||
// RoomID 返回命令所属房间,Room Cell 路由和 command log 分区依赖它。
|
||
RoomID() string
|
||
// ActorUserID 返回命令操作者,不能由具体命令字段重复推导。
|
||
ActorUserID() int64
|
||
// Type 返回稳定命令类型,持久化和恢复反序列化都依赖该字符串。
|
||
Type() string
|
||
}
|
||
|
||
// Base 承载所有房间命令都共享的元信息。
|
||
type Base struct {
|
||
// AppCode 是多 App 租户键;同一个 room_id 在不同 App 下必须完全隔离。
|
||
AppCode string `json:"app_code"`
|
||
// RequestID 是入口请求 ID,用于追踪一次外部调用。
|
||
RequestID string `json:"request_id"`
|
||
// CommandID 是幂等主键,重复命令必须返回已提交状态而不是重复变更房间。
|
||
CommandID string `json:"command_id"`
|
||
// ActorID 是操作者用户 ID,房间权限和 presence 校验以它为准。
|
||
ActorID int64 `json:"actor_user_id"`
|
||
// Room 是目标房间 ID,命令只允许作用于一个 Room Cell。
|
||
Room string `json:"room_id"`
|
||
// GatewayNodeID 记录入口节点,便于排查命令来源。
|
||
GatewayNodeID string `json:"gateway_node_id"`
|
||
// SessionID 记录 gateway 看到的客户端会话,不等同于腾讯云 IM 连接。
|
||
SessionID string `json:"session_id"`
|
||
// SentAtMS 是客户端请求进入系统的时间,恢复回放时用于还原用户态时间字段。
|
||
SentAtMS int64 `json:"sent_at_ms"`
|
||
}
|
||
|
||
// ID 返回命令幂等主键。
|
||
func (b Base) ID() string {
|
||
return b.CommandID
|
||
}
|
||
|
||
// RoomID 返回命令所属房间。
|
||
func (b Base) RoomID() string {
|
||
return b.Room
|
||
}
|
||
|
||
// ActorUserID 返回命令操作者。
|
||
func (b Base) ActorUserID() int64 {
|
||
return b.ActorID
|
||
}
|
||
|
||
// CreateRoom 定义房间创建请求。
|
||
type CreateRoom struct {
|
||
Base
|
||
// OwnerUserID 是房间所有者,创建后自动进入管理员集合。
|
||
OwnerUserID int64 `json:"owner_user_id"`
|
||
// SeatCount 决定初始化麦位数量。
|
||
SeatCount int32 `json:"seat_count"`
|
||
// Mode 记录房间模式,当前版本只保存不做复杂策略分发。
|
||
Mode string `json:"mode"`
|
||
// VisibleRegionID 是创建时固定的房间列表区域,0 表示 GLOBAL。
|
||
VisibleRegionID int64 `json:"visible_region_id"`
|
||
// OwnerCountryCode 是创建瞬间房主所属国家,列表国家筛选以该投影为准。
|
||
OwnerCountryCode string `json:"owner_country_code,omitempty"`
|
||
// RoomName 是房间展示名称,恢复时回填 RoomExt["title"],不拆成独立状态 owner。
|
||
RoomName string `json:"room_name"`
|
||
// RoomAvatar 是创建时必填的房间封面,恢复时回填 RoomExt["cover_url"]。
|
||
RoomAvatar string `json:"room_avatar"`
|
||
// RoomDescription 是房间简介,恢复时回填 RoomExt["description"]。
|
||
RoomDescription string `json:"room_description"`
|
||
// RoomShortID 是房间短 ID,首版等于创建者当前展示短号。
|
||
RoomShortID string `json:"room_short_id"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (CreateRoom) Type() string { return "create_room" }
|
||
|
||
// UpdateRoomProfile 定义房主修改房间展示资料和麦位数量的请求。
|
||
type UpdateRoomProfile struct {
|
||
Base
|
||
// RoomName 非 nil 表示更新房间展示名称;空字符串由服务层校验拒绝。
|
||
RoomName *string `json:"room_name,omitempty"`
|
||
// RoomAvatar 非 nil 表示更新房间头像;空字符串会恢复为系统默认头像。
|
||
RoomAvatar *string `json:"room_avatar,omitempty"`
|
||
// RoomDescription 非 nil 表示更新房间简介,允许清空。
|
||
RoomDescription *string `json:"room_description,omitempty"`
|
||
// SeatCount 非 nil 表示调整麦位总数,值必须来自后台启用配置。
|
||
SeatCount *int32 `json:"seat_count,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (UpdateRoomProfile) Type() string { return "update_room_profile" }
|
||
|
||
// SetRoomBackground 定义房主把已保存背景图设置为当前房间背景的请求。
|
||
type SetRoomBackground struct {
|
||
Base
|
||
// BackgroundID 必须归属于当前房间,避免房主把其他房间的素材串用到自己的房间状态。
|
||
BackgroundID int64 `json:"background_id"`
|
||
// BackgroundURL 是提交前从房间背景素材表解析出的确定性快照,恢复时不再回查列表表。
|
||
BackgroundURL string `json:"background_url"`
|
||
}
|
||
|
||
func (SetRoomBackground) Type() string { return "set_room_background" }
|
||
|
||
// JoinRoom 定义业务进房请求。
|
||
type EntryVehicleSnapshot struct {
|
||
ResourceID int64 `json:"resource_id,omitempty"`
|
||
ResourceCode string `json:"resource_code,omitempty"`
|
||
Name string `json:"name,omitempty"`
|
||
AssetURL string `json:"asset_url,omitempty"`
|
||
PreviewURL string `json:"preview_url,omitempty"`
|
||
AnimationURL string `json:"animation_url,omitempty"`
|
||
MetadataJSON string `json:"metadata_json,omitempty"`
|
||
EntitlementID string `json:"entitlement_id,omitempty"`
|
||
ExpiresAtMS int64 `json:"expires_at_ms,omitempty"`
|
||
}
|
||
|
||
type JoinRoom struct {
|
||
Base
|
||
// Role 是用户在房间内的轻量角色,默认 audience。
|
||
Role string `json:"role"`
|
||
// ActorCountryID 是 gateway 从 user-service 注入的真实国家,用于统计进房活跃国家维度。
|
||
ActorCountryID int64 `json:"actor_country_id,omitempty"`
|
||
// ActorRegionID 是 gateway 从 user-service 注入的真实区域;房间 visible_region_id 不能代替用户国家。
|
||
ActorRegionID int64 `json:"actor_region_id,omitempty"`
|
||
// EntryVehicle 是 gateway 在进房时解析出的当前有效佩戴座驾快照,用于进房 IM。
|
||
EntryVehicle EntryVehicleSnapshot `json:"entry_vehicle,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (JoinRoom) Type() string { return "join_room" }
|
||
|
||
// RoomHeartbeat 定义房间业务 presence 显式心跳。
|
||
type RoomHeartbeat struct {
|
||
Base
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (RoomHeartbeat) Type() string { return "room_heartbeat" }
|
||
|
||
// LeaveRoom 定义业务离房请求。
|
||
type LeaveRoom struct {
|
||
Base
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (LeaveRoom) Type() string { return "leave_room" }
|
||
|
||
// CloseRoom 定义房间关闭请求。
|
||
type CloseRoom struct {
|
||
Base
|
||
// Reason 记录关闭来源,例如 closed_by_owner 或 admin_closed。
|
||
Reason string `json:"reason,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (CloseRoom) Type() string { return "close_room" }
|
||
|
||
// AdminUpdateRoom 定义后台房间资料和生命周期修改请求。
|
||
type AdminUpdateRoom struct {
|
||
Base
|
||
Title *string `json:"title,omitempty"`
|
||
CoverURL *string `json:"cover_url,omitempty"`
|
||
Description *string `json:"description,omitempty"`
|
||
Mode *string `json:"mode,omitempty"`
|
||
Status *string `json:"status,omitempty"`
|
||
VisibleRegionID *int64 `json:"visible_region_id,omitempty"`
|
||
OwnerCountryCode *string `json:"owner_country_code,omitempty"`
|
||
CloseReason string `json:"close_reason,omitempty"`
|
||
AdminID uint64 `json:"admin_id,omitempty"`
|
||
AdminName string `json:"admin_name,omitempty"`
|
||
}
|
||
|
||
func (AdminUpdateRoom) Type() string { return "admin_update_room" }
|
||
|
||
// AdminDeleteRoom 定义后台删除房间请求;删除是 Room Cell 终态,不直接删物理表。
|
||
type AdminDeleteRoom struct {
|
||
Base
|
||
AdminID uint64 `json:"admin_id,omitempty"`
|
||
AdminName string `json:"admin_name,omitempty"`
|
||
}
|
||
|
||
func (AdminDeleteRoom) Type() string { return "admin_delete_room" }
|
||
|
||
// MicUp 定义上麦请求。
|
||
type MicUp struct {
|
||
Base
|
||
// SeatNo 是目标麦位编号,从 RoomState 初始化的麦位集合中查找。
|
||
SeatNo int32 `json:"seat_no"`
|
||
// MicSessionID 是服务端为本次上麦生成的发流会话 ID,确认和超时释放都必须匹配它。
|
||
MicSessionID string `json:"mic_session_id"`
|
||
// PublishDeadlineMS 是客户端必须确认 RTC 发流成功的截止时间。
|
||
PublishDeadlineMS int64 `json:"publish_deadline_ms"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (MicUp) Type() string { return "mic_up" }
|
||
|
||
// MicDown 定义下麦请求。
|
||
type MicDown struct {
|
||
Base
|
||
// TargetUserID 是被下麦用户;操作者和目标用户可以不同。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// MicSessionID 非空时只允许释放同一发流会话,防止旧超时任务清掉新上麦。
|
||
MicSessionID string `json:"mic_session_id,omitempty"`
|
||
// Reason 记录系统触发下麦原因,例如 publish_timeout;主动下麦保持为空。
|
||
Reason string `json:"reason,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (MicDown) Type() string { return "mic_down" }
|
||
|
||
// ConfirmMicPublishing 定义 RTC 发流成功确认请求。
|
||
type ConfirmMicPublishing struct {
|
||
Base
|
||
// TargetUserID 是被确认发布音频的用户;为空时由服务层使用 ActorUserID。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// MicSessionID 必须匹配当前麦位上的会话。
|
||
MicSessionID string `json:"mic_session_id"`
|
||
// RoomVersion 是触发客户端/RTC 事件时看到的房间版本,旧版本事件会被忽略。
|
||
RoomVersion int64 `json:"room_version"`
|
||
// EventTimeMS 是客户端 SDK 或 RTC webhook 的事件时间,必须晚于已接受事件。
|
||
EventTimeMS int64 `json:"event_time_ms"`
|
||
// Source 记录确认来源,例如 client 或 rtc_webhook。
|
||
Source string `json:"source,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (ConfirmMicPublishing) Type() string { return "confirm_mic_publishing" }
|
||
|
||
// MicHeartbeat 定义当前麦位会话的服务端心跳刷新请求。
|
||
type MicHeartbeat struct {
|
||
Base
|
||
// TargetUserID 是被刷新麦上心跳的用户;为空时服务层使用 ActorUserID。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// MicSessionID 必须匹配当前麦位会话,避免旧客户端心跳续住新麦位。
|
||
MicSessionID string `json:"mic_session_id"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (MicHeartbeat) Type() string { return "mic_heartbeat" }
|
||
|
||
// SetMicMute 定义麦克风静音状态同步请求。
|
||
type SetMicMute struct {
|
||
Base
|
||
// TargetUserID 是被修改麦克风静音态的麦上用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// Muted 为 true 表示麦克风静音,false 表示取消静音。
|
||
Muted bool `json:"muted"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SetMicMute) Type() string { return "set_mic_mute" }
|
||
|
||
// ApplyRTCEvent 定义由可信腾讯 RTC 服务端回调驱动的房间状态事件。
|
||
type ApplyRTCEvent struct {
|
||
Base
|
||
// TargetUserID 是腾讯 RTC 回调中的 UserId,必须映射回内部长 user_id。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// EventType 是 gateway 标准化后的 RTC 事件,当前为 audio_started/audio_stopped/room_exited。
|
||
EventType string `json:"event_type"`
|
||
// EventTimeMS 是腾讯 RTC 回调中的 EventMsTs,用于丢弃旧事件。
|
||
EventTimeMS int64 `json:"event_time_ms"`
|
||
// Reason 保留腾讯回调 reason 和入口语义,便于 IM 房间消息和审计排障。
|
||
Reason string `json:"reason,omitempty"`
|
||
// Source 记录确认来源,当前为 tencent_rtc_callback。
|
||
Source string `json:"source,omitempty"`
|
||
// ExternalEventID 是由 gateway 从腾讯事件字段派生的稳定事件标识。
|
||
ExternalEventID string `json:"external_event_id,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (ApplyRTCEvent) Type() string { return "apply_rtc_event" }
|
||
|
||
// ChangeMicSeat 定义换麦位请求。
|
||
type ChangeMicSeat struct {
|
||
Base
|
||
// TargetUserID 是需要换位的用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// SeatNo 是目标麦位编号。
|
||
SeatNo int32 `json:"seat_no"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (ChangeMicSeat) Type() string { return "change_mic_seat" }
|
||
|
||
// SetMicSeatLock 定义锁定或解锁麦位请求。
|
||
type SetMicSeatLock struct {
|
||
Base
|
||
// SeatNo 是目标麦位编号。
|
||
SeatNo int32 `json:"seat_no"`
|
||
// Locked 为 true 表示锁定,false 表示解锁。
|
||
Locked bool `json:"locked"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SetMicSeatLock) Type() string { return "set_mic_seat_lock" }
|
||
|
||
// SetChatEnabled 定义房间公屏开关请求。
|
||
type SetChatEnabled struct {
|
||
Base
|
||
// Enabled 为 false 时 CheckSpeakPermission 会拒绝所有普通公屏发言。
|
||
Enabled bool `json:"enabled"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SetChatEnabled) Type() string { return "set_chat_enabled" }
|
||
|
||
// SetRoomPassword 定义房主设置或清空入房密码的请求。
|
||
type SetRoomPassword struct {
|
||
Base
|
||
// Locked 为 false 表示清空密码;true 表示开启入房密码校验。
|
||
Locked bool `json:"locked"`
|
||
// Password 只在当前进程内参与 command_id 冲突判断,序列化时必须省略,避免明文进入 command log。
|
||
Password string `json:"-"`
|
||
// PasswordHash 是服务端生成的密码哈希,command log 不能保存客户端明文。
|
||
PasswordHash string `json:"password_hash,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SetRoomPassword) Type() string { return "set_room_password" }
|
||
|
||
// SetRoomAdmin 定义添加或移除管理员请求。
|
||
type SetRoomAdmin struct {
|
||
Base
|
||
// TargetUserID 是被添加或移除管理员身份的用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// Enabled 为 true 表示添加管理员,false 表示移除管理员。
|
||
Enabled bool `json:"enabled"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SetRoomAdmin) Type() string { return "set_room_admin" }
|
||
|
||
// MuteUser 定义禁言请求。
|
||
type MuteUser struct {
|
||
Base
|
||
// TargetUserID 是被修改禁言状态的用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// Muted 为 true 表示禁言,false 表示解除禁言。
|
||
Muted bool `json:"muted"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (MuteUser) Type() string { return "mute_user" }
|
||
|
||
// KickUser 定义踢人请求。
|
||
type KickUser struct {
|
||
Base
|
||
// TargetUserID 是被踢出并加入 ban 集合的用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// DurationMS 为 0 表示永久 ban;大于 0 表示从命令提交时间起禁止进房的时长。
|
||
DurationMS int64 `json:"duration_ms"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (KickUser) Type() string { return "kick_user" }
|
||
|
||
// SystemEvictUser 定义平台级封禁、风控或后台治理触发的房间驱逐请求。
|
||
type SystemEvictUser struct {
|
||
Base
|
||
// TargetUserID 是被系统驱逐的房间用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// OperatorUserID 是触发治理动作的后台管理员或 App 管理员,不参与房间权限判断。
|
||
OperatorUserID int64 `json:"operator_user_id"`
|
||
// Reason 记录封禁、风控或后台动作来源,进入 command log 便于审计追踪。
|
||
Reason string `json:"reason"`
|
||
// BanFromRoom 控制是否写入当前房间 ban 集合。
|
||
BanFromRoom bool `json:"ban_from_room"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SystemEvictUser) Type() string { return "system_evict_user" }
|
||
|
||
// UnbanUser 定义解除房间 ban 请求。
|
||
type UnbanUser struct {
|
||
Base
|
||
// TargetUserID 是被解除 ban 的用户。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (UnbanUser) Type() string { return "unban_user" }
|
||
|
||
// SendGift 定义送礼请求。
|
||
type SendGift struct {
|
||
Base
|
||
// TargetType 当前只支持 user;保留字段避免后续 all_mic/all_room/couple 再改命令结构。
|
||
TargetType string `json:"target_type"`
|
||
// TargetUserIDs 是新协议目标用户集合;target_type=user 时允许多个房间内用户。
|
||
TargetUserIDs []int64 `json:"target_user_ids,omitempty"`
|
||
// TargetUserID 是兼容单目标字段和批量主目标,所有接收方仍要求在房间内。
|
||
TargetUserID int64 `json:"target_user_id"`
|
||
// GiftID 是礼物配置标识,具体配置有效性当前由调用方和 wallet 链路保证。
|
||
GiftID string `json:"gift_id"`
|
||
// PoolID 是幸运礼物奖池标识;为空表示普通礼物或默认幸运奖池。
|
||
PoolID string `json:"pool_id,omitempty"`
|
||
// GiftCount 是礼物数量,必须为正数。
|
||
GiftCount int32 `json:"gift_count"`
|
||
// SenderRegionID 是送礼用户所属区域,由 gateway 从 user-service 注入。
|
||
SenderRegionID int64 `json:"sender_region_id,omitempty"`
|
||
// SenderCountryID 是送礼用户所属国家,由 gateway 从 user-service 注入,统计服务不能用房间区域反推。
|
||
SenderCountryID int64 `json:"sender_country_id,omitempty"`
|
||
// TargetIsHost 是 gateway 从 user-service active host profile 注入的工资入账开关。
|
||
TargetIsHost bool `json:"target_is_host,omitempty"`
|
||
// TargetHostRegionID 是工资政策匹配区域;room-service 不自行推导主播身份或区域。
|
||
TargetHostRegionID int64 `json:"target_host_region_id,omitempty"`
|
||
// TargetAgencyOwnerUserID 是 gateway 从 user-service active host profile 注入的代理收款人快照。
|
||
TargetAgencyOwnerUserID int64 `json:"target_agency_owner_user_id,omitempty"`
|
||
// BillingReceiptID 是 wallet-service 成功落账后的回执,用于排障和恢复审计。
|
||
BillingReceiptID string `json:"billing_receipt_id,omitempty"`
|
||
// CoinSpent 是 sender COIN 实际扣减值,来自 wallet-service 服务端价格。
|
||
CoinSpent int64 `json:"coin_spent,omitempty"`
|
||
// GiftPointAdded 是历史命令字段;GIFT_POINT 已下线,新送礼不再赋值,恢复逻辑只依赖 HeatValue。
|
||
GiftPointAdded int64 `json:"gift_point_added,omitempty"`
|
||
// HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。
|
||
HeatValue int64 `json:"heat_value,omitempty"`
|
||
// PriceVersion 是本次钱包结算使用的服务端礼物价格版本。
|
||
PriceVersion string `json:"price_version,omitempty"`
|
||
// GiftTypeCode 是 wallet-service 结算时锁定的礼物类型,用于火箭礼物类型燃料规则。
|
||
GiftTypeCode string `json:"gift_type_code,omitempty"`
|
||
// HostPeriodDiamondAdded 是 wallet-service 给主播周期钻石账户本次入账的钻石数。
|
||
HostPeriodDiamondAdded int64 `json:"host_period_diamond_added,omitempty"`
|
||
// HostPeriodCycleKey 是本次周期钻石入账所属 UTC 月周期。
|
||
HostPeriodCycleKey string `json:"host_period_cycle_key,omitempty"`
|
||
// RocketTouched 表示本次送礼已经计算过火箭状态,恢复时直接使用下列确定性快照。
|
||
RocketTouched bool `json:"rocket_touched,omitempty"`
|
||
// RocketAddedFuel 是按后台规则算出的理论燃料,倒计时和溢出规则会让它不完全生效。
|
||
RocketAddedFuel int64 `json:"rocket_added_fuel,omitempty"`
|
||
// RocketEffectiveFuel 是实际写入当前等级进度的燃料,溢出不会进入下一级。
|
||
RocketEffectiveFuel int64 `json:"rocket_effective_fuel,omitempty"`
|
||
// RocketOverflowFuel 是本次礼物在当前等级阈值之外被丢弃的燃料。
|
||
RocketOverflowFuel int64 `json:"rocket_overflow_fuel,omitempty"`
|
||
// RocketLevel/RocketFuel/RocketStatus 是送礼后火箭状态快照。
|
||
RocketLevel int32 `json:"rocket_level,omitempty"`
|
||
RocketFuel int64 `json:"rocket_fuel,omitempty"`
|
||
RocketStatus string `json:"rocket_status,omitempty"`
|
||
// RocketFuelThreshold 是当前等级阈值快照。
|
||
RocketFuelThreshold int64 `json:"rocket_fuel_threshold,omitempty"`
|
||
// RocketIgnitedAtMS/RocketLaunchAtMS 只在点火进入倒计时时写入。
|
||
RocketIgnitedAtMS int64 `json:"rocket_ignited_at_ms,omitempty"`
|
||
RocketLaunchAtMS int64 `json:"rocket_launch_at_ms,omitempty"`
|
||
// RocketResetAtMS 是该火箭进度所属 UTC 日的下一次重置时间。
|
||
RocketResetAtMS int64 `json:"rocket_reset_at_ms,omitempty"`
|
||
// RocketTop1UserID/RocketIgniterUserID 在燃料满瞬间锁定,离房后仍用它们发奖。
|
||
RocketTop1UserID int64 `json:"rocket_top1_user_id,omitempty"`
|
||
RocketIgniterUserID int64 `json:"rocket_igniter_user_id,omitempty"`
|
||
// RocketID 是本轮火箭幂等 ID,发射、抽奖和 IM 都围绕它关联。
|
||
RocketID string `json:"rocket_id,omitempty"`
|
||
// RocketConfigVersion 是本轮火箭采用的后台配置版本。
|
||
RocketConfigVersion int64 `json:"rocket_config_version,omitempty"`
|
||
// RocketPendingLaunches 是送礼提交后的待发射队列,恢复时不能只保存当前进度。
|
||
RocketPendingLaunches []RocketPendingLaunch `json:"rocket_pending_launches,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (SendGift) Type() string { return "send_gift" }
|
||
|
||
// RocketRewardGrant 记录发射命令中已经结算出的资源组发放结果。
|
||
type RocketRewardGrant struct {
|
||
RewardRole string `json:"reward_role"`
|
||
UserID int64 `json:"user_id"`
|
||
RewardItemID string `json:"reward_item_id"`
|
||
ResourceGroupID int64 `json:"resource_group_id"`
|
||
DisplayName string `json:"display_name,omitempty"`
|
||
IconURL string `json:"icon_url,omitempty"`
|
||
GrantID string `json:"grant_id,omitempty"`
|
||
Status string `json:"status"`
|
||
}
|
||
|
||
// RocketPendingLaunch 记录一枚已点火火箭的延迟发射快照。
|
||
type RocketPendingLaunch struct {
|
||
RocketID string `json:"rocket_id"`
|
||
Level int32 `json:"level"`
|
||
FuelThreshold int64 `json:"fuel_threshold"`
|
||
IgnitedAtMS int64 `json:"ignited_at_ms"`
|
||
LaunchAtMS int64 `json:"launch_at_ms"`
|
||
ResetAtMS int64 `json:"reset_at_ms"`
|
||
Top1UserID int64 `json:"top1_user_id,omitempty"`
|
||
IgniterUserID int64 `json:"igniter_user_id,omitempty"`
|
||
ConfigVersion int64 `json:"config_version"`
|
||
CoverURL string `json:"cover_url,omitempty"`
|
||
}
|
||
|
||
// LaunchRoomRocket 定义系统到点发射命令。
|
||
type LaunchRoomRocket struct {
|
||
Base
|
||
RocketID string `json:"rocket_id"`
|
||
Level int32 `json:"level"`
|
||
// Current* 是发射命令提交后的当前进度快照;发射不推进当前等级,只移除待发射队列。
|
||
CurrentLevel int32 `json:"current_level"`
|
||
CurrentFuel int64 `json:"current_fuel"`
|
||
CurrentFuelThreshold int64 `json:"current_fuel_threshold"`
|
||
CurrentStatus string `json:"current_status"`
|
||
CurrentRocketID string `json:"current_rocket_id,omitempty"`
|
||
NextLevel int32 `json:"next_level"`
|
||
NextFuelThreshold int64 `json:"next_fuel_threshold"`
|
||
ConfigVersion int64 `json:"config_version"`
|
||
ResetAtMS int64 `json:"reset_at_ms"`
|
||
Top1UserID int64 `json:"top1_user_id,omitempty"`
|
||
IgniterUserID int64 `json:"igniter_user_id,omitempty"`
|
||
LaunchedAtMS int64 `json:"launched_at_ms"`
|
||
InRoomUserIDs []int64 `json:"in_room_user_ids,omitempty"`
|
||
Rewards []RocketRewardGrant `json:"rewards,omitempty"`
|
||
PendingLaunches []RocketPendingLaunch `json:"pending_launches,omitempty"`
|
||
}
|
||
|
||
// Type 返回命令类型。
|
||
func (LaunchRoomRocket) Type() string { return "launch_room_rocket" }
|
||
|
||
// Serialize 把命令编码成 command log 持久化所需的稳定载荷。
|
||
func Serialize(cmd Command) ([]byte, error) {
|
||
// 当前 command log 使用 JSON 载荷,便于排查;命令类型由独立列保存。
|
||
return json.Marshal(cmd)
|
||
}
|
||
|
||
// IdempotencyPayloadForCommand 生成用于 command_id 冲突判定的业务载荷。
|
||
func IdempotencyPayloadForCommand(cmd Command) ([]byte, error) {
|
||
payload, err := Serialize(cmd)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return IdempotencyPayload(payload)
|
||
}
|
||
|
||
// IdempotencyPayload 去掉请求追踪和入口节点等易变字段,只保留业务命令语义。
|
||
func IdempotencyPayload(payload []byte) ([]byte, error) {
|
||
var values map[string]any
|
||
if err := json.Unmarshal(payload, &values); err != nil {
|
||
return nil, err
|
||
}
|
||
delete(values, "request_id")
|
||
delete(values, "gateway_node_id")
|
||
delete(values, "session_id")
|
||
delete(values, "sent_at_ms")
|
||
// SendGift 的以下字段由 wallet-service 结算后写入 command log,不属于客户端请求幂等语义。
|
||
delete(values, "billing_receipt_id")
|
||
delete(values, "coin_spent")
|
||
delete(values, "gift_point_added")
|
||
delete(values, "heat_value")
|
||
delete(values, "price_version")
|
||
delete(values, "gift_type_code")
|
||
delete(values, "host_period_diamond_added")
|
||
delete(values, "host_period_cycle_key")
|
||
delete(values, "rocket_touched")
|
||
delete(values, "rocket_added_fuel")
|
||
delete(values, "rocket_effective_fuel")
|
||
delete(values, "rocket_overflow_fuel")
|
||
delete(values, "rocket_level")
|
||
delete(values, "rocket_fuel")
|
||
delete(values, "rocket_status")
|
||
delete(values, "rocket_fuel_threshold")
|
||
delete(values, "rocket_ignited_at_ms")
|
||
delete(values, "rocket_launch_at_ms")
|
||
delete(values, "rocket_reset_at_ms")
|
||
delete(values, "rocket_top1_user_id")
|
||
delete(values, "rocket_igniter_user_id")
|
||
delete(values, "rocket_id")
|
||
delete(values, "rocket_config_version")
|
||
delete(values, "rocket_pending_launches")
|
||
// LaunchRoomRocket 的以下字段是 worker 在发射时结算出的结果,不属于触发发射的幂等语义。
|
||
delete(values, "current_level")
|
||
delete(values, "current_fuel")
|
||
delete(values, "current_fuel_threshold")
|
||
delete(values, "current_status")
|
||
delete(values, "current_rocket_id")
|
||
delete(values, "config_version")
|
||
delete(values, "reset_at_ms")
|
||
delete(values, "top1_user_id")
|
||
delete(values, "igniter_user_id")
|
||
delete(values, "launched_at_ms")
|
||
delete(values, "in_room_user_ids")
|
||
delete(values, "rewards")
|
||
delete(values, "pending_launches")
|
||
// SetRoomPassword 的哈希带随机盐;通用 payload 比较先去掉哈希,service 层再用 transient 明文和已存 bcrypt 哈希确认是否同一密码。
|
||
delete(values, "password_hash")
|
||
// MicUp 的发流确认 deadline 由 room-service 生成,客户端重试同一 command_id 时不能因此冲突。
|
||
delete(values, "publish_deadline_ms")
|
||
|
||
return json.Marshal(values)
|
||
}
|
||
|
||
// Deserialize 按命令类型恢复命令载荷。
|
||
func Deserialize(commandType string, payload []byte) (Command, error) {
|
||
var cmd Command
|
||
|
||
// command_type 是恢复时的分发键;未知类型表示当前代码无法保证恢复语义。
|
||
switch commandType {
|
||
case CreateRoom{}.Type():
|
||
cmd = &CreateRoom{}
|
||
case UpdateRoomProfile{}.Type():
|
||
cmd = &UpdateRoomProfile{}
|
||
case SetRoomBackground{}.Type():
|
||
cmd = &SetRoomBackground{}
|
||
case JoinRoom{}.Type():
|
||
cmd = &JoinRoom{}
|
||
case RoomHeartbeat{}.Type():
|
||
cmd = &RoomHeartbeat{}
|
||
case LeaveRoom{}.Type():
|
||
cmd = &LeaveRoom{}
|
||
case CloseRoom{}.Type():
|
||
cmd = &CloseRoom{}
|
||
case AdminUpdateRoom{}.Type():
|
||
cmd = &AdminUpdateRoom{}
|
||
case AdminDeleteRoom{}.Type():
|
||
cmd = &AdminDeleteRoom{}
|
||
case MicUp{}.Type():
|
||
cmd = &MicUp{}
|
||
case MicDown{}.Type():
|
||
cmd = &MicDown{}
|
||
case ConfirmMicPublishing{}.Type():
|
||
cmd = &ConfirmMicPublishing{}
|
||
case MicHeartbeat{}.Type():
|
||
cmd = &MicHeartbeat{}
|
||
case SetMicMute{}.Type():
|
||
cmd = &SetMicMute{}
|
||
case ApplyRTCEvent{}.Type():
|
||
cmd = &ApplyRTCEvent{}
|
||
case ChangeMicSeat{}.Type():
|
||
cmd = &ChangeMicSeat{}
|
||
case SetMicSeatLock{}.Type():
|
||
cmd = &SetMicSeatLock{}
|
||
case SetChatEnabled{}.Type():
|
||
cmd = &SetChatEnabled{}
|
||
case SetRoomPassword{}.Type():
|
||
cmd = &SetRoomPassword{}
|
||
case SetRoomAdmin{}.Type():
|
||
cmd = &SetRoomAdmin{}
|
||
case MuteUser{}.Type():
|
||
cmd = &MuteUser{}
|
||
case KickUser{}.Type():
|
||
cmd = &KickUser{}
|
||
case SystemEvictUser{}.Type():
|
||
cmd = &SystemEvictUser{}
|
||
case UnbanUser{}.Type():
|
||
cmd = &UnbanUser{}
|
||
case SendGift{}.Type():
|
||
cmd = &SendGift{}
|
||
case LaunchRoomRocket{}.Type():
|
||
cmd = &LaunchRoomRocket{}
|
||
default:
|
||
return nil, fmt.Errorf("unknown room command type: %s", commandType)
|
||
}
|
||
|
||
if err := json.Unmarshal(payload, cmd); err != nil {
|
||
// 单条命令反序列化失败说明 command log 损坏,恢复流程必须失败而不是继续构建错误状态。
|
||
return nil, err
|
||
}
|
||
|
||
return cmd, nil
|
||
}
|