package service import ( "time" "google.golang.org/protobuf/proto" roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/command" ) func marshalMutationResult(result mutationResult) ([]byte, error) { if result.giftV2 == nil { return nil, nil } payload, err := proto.Marshal(result.giftV2) if err != nil { return nil, xerr.New(xerr.Internal, "send gift v2 result cannot be encoded") } return payload, nil } func replayMutationResult(record CommandRecord, snapshot *roomv1.RoomSnapshot) (mutationResult, error) { result := mutationResult{snapshot: snapshot} if record.CommandType != (command.SendGift{}).Type() || len(record.ResultPayload) == 0 { // 历史命令没有 typed result;继续返回当前 snapshot,保持 V1 已上线行为,不伪造账务字段。 return result, nil } var stored roomv1.SendGiftResultV2 if err := proto.Unmarshal(record.ResultPayload, &stored); err != nil { // 已提交结果损坏时必须 fail-close,不能重跑 wallet 或幸运礼物来“修复”一个已提交命令。 return mutationResult{}, xerr.New(xerr.Internal, "stored send gift result cannot be decoded") } replayed := proto.Clone(&stored).(*roomv1.SendGiftResultV2) replayed.IdempotentReplay = true replayed.OperationStatus = GiftOperationStatusCommitted result.billingReceiptID = replayed.GetBillingReceiptId() result.coinBalanceAfter = replayed.GetCoinBalanceAfter() result.giftIncomeBalanceAfter = replayed.GetGiftIncomeBalanceAfter() result.roomHeat = replayed.GetRoomHeat() result.giftRank = cloneGiftRankItems(replayed.GetGiftRank()) result.luckyGift = replayed.GetLuckyGift() result.luckyGifts = append([]*roomv1.LuckyGiftDrawResult(nil), replayed.GetLuckyGifts()...) result.batchDisplay = replayed.GetBatchDisplay() result.giftV2 = replayed return result, nil } func cloneGiftRankItems(items []*roomv1.RankItem) []*roomv1.RankItem { out := make([]*roomv1.RankItem, 0, len(items)) for _, item := range items { if item != nil { out = append(out, proto.Clone(item).(*roomv1.RankItem)) } } return out } func luckyHitsFromResults(results []*roomv1.LuckyGiftDrawResult) []*roomv1.SendGiftLuckyHit { hits := make([]*roomv1.SendGiftLuckyHit, 0) for _, result := range results { if result == nil { continue } for _, hit := range result.GetHits() { if hit != nil && hit.GetRewardCoins() > 0 { hits = append(hits, proto.Clone(hit).(*roomv1.SendGiftLuckyHit)) } } } return hits } func giftOperationCommitForResult(cmd command.Command, resultPayload []byte, now time.Time) *GiftOperationCommit { if cmd == nil || cmd.Type() != (command.SendGift{}).Type() || len(resultPayload) == 0 { return nil } return &GiftOperationCommit{ CommandID: cmd.ID(), ResultPayload: resultPayload, UpdatedAtMS: now.UnixMilli(), } }