bindbox-game/internal/service/snapshot/snapshot_service.go

263 lines
7.7 KiB
Go

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
}