package snapshot import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "time" "bindbox-game/internal/repository/mysql" "bindbox-game/internal/repository/mysql/model" ) // UserStateSnapshot 用户状态快照 type UserStateSnapshot struct { SnapshotTime time.Time `json:"snapshot_time"` User *UserInfo `json:"user"` Points *PointsInfo `json:"points"` Coupons []*CouponInfo `json:"coupons"` ItemCards []*ItemCardInfo `json:"item_cards"` InventoryCount int64 `json:"inventory_count"` } // UserInfo 用户基本信息 type UserInfo struct { ID int64 `json:"id"` Nickname string `json:"nickname"` Mobile string `json:"mobile"` } // PointsInfo 积分信息 type PointsInfo struct { Balance int64 `json:"balance"` Version int32 `json:"version"` } // CouponInfo 优惠券信息 type CouponInfo struct { UserCouponID int64 `json:"user_coupon_id"` CouponID int64 `json:"coupon_id"` CouponName string `json:"coupon_name"` Status int32 `json:"status"` BalanceAmount int64 `json:"balance_amount"` ValidEnd time.Time `json:"valid_end"` } // ItemCardInfo 道具卡信息 type ItemCardInfo struct { UserItemCardID int64 `json:"user_item_card_id"` CardID int64 `json:"card_id"` CardName string `json:"card_name"` Status int32 `json:"status"` ValidEnd time.Time `json:"valid_end"` } // SnapshotDiff 快照差异 type SnapshotDiff struct { PointsChanged int64 `json:"points_changed"` CouponsUsed []*CouponInfo `json:"coupons_used"` ItemCardsUsed []*ItemCardInfo `json:"item_cards_used"` InventoryAdded int64 `json:"inventory_added"` } // Service 快照服务 type Service interface { // CaptureUserState 采集用户当前状态 CaptureUserState(ctx context.Context, userID int64) (*UserStateSnapshot, error) // SaveSnapshot 保存快照 SaveSnapshot(ctx context.Context, orderID int64, orderNo string, userID int64, snapshotType int32, snapshot *UserStateSnapshot) error // GetSnapshotsByOrderID 获取订单快照 GetSnapshotsByOrderID(ctx context.Context, orderID int64) (*model.OrderSnapshots, *model.OrderSnapshots, error) // CompareSnapshots 对比快照差异 CompareSnapshots(before, after *UserStateSnapshot) *SnapshotDiff } type service struct { db mysql.Repo } // NewService 创建快照服务 func NewService(db mysql.Repo) Service { return &service{db: db} } // CaptureUserState 采集用户当前状态 func (s *service) CaptureUserState(ctx context.Context, userID int64) (*UserStateSnapshot, error) { snapshot := &UserStateSnapshot{ SnapshotTime: time.Now(), } // 1. 查询用户基本信息 var user model.Users if err := s.db.GetDbR().WithContext(ctx).Where("id = ?", userID).First(&user).Error; err == nil { snapshot.User = &UserInfo{ ID: user.ID, Nickname: user.Nickname, Mobile: user.Mobile, } } // 2. 查询用户积分 var points model.UserPoints if err := s.db.GetDbR().WithContext(ctx).Where("user_id = ?", userID).First(&points).Error; err == nil { snapshot.Points = &PointsInfo{ Balance: points.Points, Version: points.Version, } } else { snapshot.Points = &PointsInfo{Balance: 0, Version: 0} } // 3. 查询未使用的优惠券 var userCoupons []model.UserCoupons s.db.GetDbR().WithContext(ctx).Where("user_id = ? AND status = 1", userID).Find(&userCoupons) if len(userCoupons) > 0 { couponIDs := make([]int64, 0, len(userCoupons)) for _, uc := range userCoupons { couponIDs = append(couponIDs, uc.CouponID) } var systemCoupons []model.SystemCoupons s.db.GetDbR().WithContext(ctx).Where("id IN ?", couponIDs).Find(&systemCoupons) couponNameMap := make(map[int64]string) for _, sc := range systemCoupons { couponNameMap[sc.ID] = sc.Name } for _, uc := range userCoupons { snapshot.Coupons = append(snapshot.Coupons, &CouponInfo{ UserCouponID: uc.ID, CouponID: uc.CouponID, CouponName: couponNameMap[uc.CouponID], Status: uc.Status, BalanceAmount: uc.BalanceAmount, ValidEnd: uc.ValidEnd, }) } } // 4. 查询未使用的道具卡 var userItemCards []model.UserItemCards s.db.GetDbR().WithContext(ctx).Where("user_id = ? AND status = 1", userID).Find(&userItemCards) if len(userItemCards) > 0 { cardIDs := make([]int64, 0, len(userItemCards)) for _, uc := range userItemCards { cardIDs = append(cardIDs, uc.CardID) } var systemCards []model.SystemItemCards s.db.GetDbR().WithContext(ctx).Where("id IN ?", cardIDs).Find(&systemCards) cardNameMap := make(map[int64]string) for _, sc := range systemCards { cardNameMap[sc.ID] = sc.Name } for _, uc := range userItemCards { snapshot.ItemCards = append(snapshot.ItemCards, &ItemCardInfo{ UserItemCardID: uc.ID, CardID: uc.CardID, CardName: cardNameMap[uc.CardID], Status: uc.Status, ValidEnd: uc.ValidEnd, }) } } // 5. 查询资产数量 var inventoryCount int64 s.db.GetDbR().WithContext(ctx).Model(&model.UserInventory{}).Where("user_id = ? AND status = 1", userID).Count(&inventoryCount) snapshot.InventoryCount = inventoryCount return snapshot, nil } // SaveSnapshot 保存快照 func (s *service) SaveSnapshot(ctx context.Context, orderID int64, orderNo string, userID int64, snapshotType int32, snapshot *UserStateSnapshot) error { data, err := json.Marshal(snapshot) if err != nil { return err } // 计算hash hash := sha256.Sum256(data) hashStr := hex.EncodeToString(hash[:]) record := &model.OrderSnapshots{ OrderID: orderID, OrderNo: orderNo, UserID: userID, SnapshotType: snapshotType, SnapshotData: string(data), SnapshotHash: hashStr, } return s.db.GetDbW().WithContext(ctx).Create(record).Error } // GetSnapshotsByOrderID 获取订单快照 func (s *service) GetSnapshotsByOrderID(ctx context.Context, orderID int64) (*model.OrderSnapshots, *model.OrderSnapshots, error) { var snapshots []model.OrderSnapshots if err := s.db.GetDbR().WithContext(ctx).Where("order_id = ?", orderID).Order("snapshot_type ASC").Find(&snapshots).Error; err != nil { return nil, nil, err } var before, after *model.OrderSnapshots for i := range snapshots { if snapshots[i].SnapshotType == 1 { before = &snapshots[i] } else if snapshots[i].SnapshotType == 2 { after = &snapshots[i] } } return before, after, nil } // CompareSnapshots 对比快照差异 func (s *service) CompareSnapshots(before, after *UserStateSnapshot) *SnapshotDiff { diff := &SnapshotDiff{} // 积分差异 if before.Points != nil && after.Points != nil { diff.PointsChanged = after.Points.Balance - before.Points.Balance } // 优惠券使用情况(消费前有,消费后没有或状态变化) afterCouponMap := make(map[int64]*CouponInfo) for _, c := range after.Coupons { afterCouponMap[c.UserCouponID] = c } for _, c := range before.Coupons { if _, ok := afterCouponMap[c.UserCouponID]; !ok { // 消费后不存在,说明被使用了 diff.CouponsUsed = append(diff.CouponsUsed, c) } else if afterCouponMap[c.UserCouponID].Status != c.Status { // 状态变化,说明被使用了 diff.CouponsUsed = append(diff.CouponsUsed, c) } } // 道具卡使用情况 afterCardMap := make(map[int64]*ItemCardInfo) for _, c := range after.ItemCards { afterCardMap[c.UserItemCardID] = c } for _, c := range before.ItemCards { if _, ok := afterCardMap[c.UserItemCardID]; !ok { diff.ItemCardsUsed = append(diff.ItemCardsUsed, c) } else if afterCardMap[c.UserItemCardID].Status != c.Status { diff.ItemCardsUsed = append(diff.ItemCardsUsed, c) } } // 资产增加 diff.InventoryAdded = after.InventoryCount - before.InventoryCount return diff }