package admin import ( "encoding/json" "net/http" "strconv" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/service/snapshot" ) type getOrderSnapshotsResponse struct { Order struct { ID int64 `json:"id"` OrderNo string `json:"order_no"` UserID int64 `json:"user_id"` Status int32 `json:"status"` ActualAmount int64 `json:"actual_amount"` PaidAt string `json:"paid_at"` } `json:"order"` BeforeSnapshot *snapshot.UserStateSnapshot `json:"before_snapshot"` AfterSnapshot *snapshot.UserStateSnapshot `json:"after_snapshot"` Diff *snapshot.SnapshotDiff `json:"diff"` HasSnapshots bool `json:"has_snapshots"` } // GetOrderSnapshots 获取订单快照 // @Summary 获取订单审计快照 // @Description 获取订单消费前后的完整用户状态快照 // @Tags Admin-Audit // @Accept json // @Produce json // @Param order_id path int true "订单ID" // @Success 200 {object} getOrderSnapshotsResponse "成功" // @Router /api/admin/orders/{order_id}/snapshots [get] func (h *handler) GetOrderSnapshots() core.HandlerFunc { return func(ctx core.Context) { orderIDStr := ctx.Param("order_id") orderID, err := strconv.ParseInt(orderIDStr, 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的订单ID")) return } // 查询订单 order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(orderID)).First() if err != nil || order == nil { ctx.AbortWithError(core.Error(http.StatusNotFound, 21003, "订单不存在")) return } rsp := &getOrderSnapshotsResponse{} rsp.Order.ID = order.ID rsp.Order.OrderNo = order.OrderNo rsp.Order.UserID = order.UserID rsp.Order.Status = order.Status rsp.Order.ActualAmount = order.ActualAmount if !order.PaidAt.IsZero() { rsp.Order.PaidAt = order.PaidAt.Format("2006-01-02 15:04:05") } // 使用快照服务获取快照 beforeSnap, afterSnap, err := h.snapshotSvc.GetSnapshotsByOrderID(ctx.RequestContext(), orderID) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, 21010, err.Error())) return } if beforeSnap == nil && afterSnap == nil { rsp.HasSnapshots = false ctx.Payload(rsp) return } rsp.HasSnapshots = true // 解析快照数据 if beforeSnap != nil { var before snapshot.UserStateSnapshot if err := json.Unmarshal([]byte(beforeSnap.SnapshotData), &before); err == nil { rsp.BeforeSnapshot = &before } } if afterSnap != nil { var after snapshot.UserStateSnapshot if err := json.Unmarshal([]byte(afterSnap.SnapshotData), &after); err == nil { rsp.AfterSnapshot = &after } } // 计算差异 if rsp.BeforeSnapshot != nil && rsp.AfterSnapshot != nil { rsp.Diff = h.snapshotSvc.CompareSnapshots(rsp.BeforeSnapshot, rsp.AfterSnapshot) } ctx.Payload(rsp) } } type rollbackOrderRequest struct { Reason string `json:"reason" form:"reason"` Confirm bool `json:"confirm" form:"confirm"` } type rollbackOrderResponse struct { Success bool `json:"success"` RollbackLogID int64 `json:"rollback_log_id"` PointsRestored int64 `json:"points_restored"` CouponsRestored int `json:"coupons_restored"` ItemCardsRestored int `json:"item_cards_restored"` InventoryRevoked int `json:"inventory_revoked"` RefundAmount int64 `json:"refund_amount"` ErrorMsg string `json:"error_msg,omitempty"` } // RollbackOrder 回滚订单 // @Summary 回滚订单到消费前状态 // @Description 基于快照将用户数据回滚到消费前状态,包括恢复积分、优惠券、道具卡,作废资产,调用微信退款 // @Tags Admin-Audit // @Accept json // @Produce json // @Param order_id path int true "订单ID" // @Param request body rollbackOrderRequest true "回滚请求" // @Success 200 {object} rollbackOrderResponse "成功" // @Router /api/admin/orders/{order_id}/rollback [post] func (h *handler) RollbackOrder() core.HandlerFunc { return func(ctx core.Context) { orderIDStr := ctx.Param("order_id") orderID, err := strconv.ParseInt(orderIDStr, 10, 64) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的订单ID")) return } req := new(rollbackOrderRequest) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if !req.Confirm { ctx.AbortWithError(core.Error(http.StatusBadRequest, 21011, "请确认回滚操作")) return } // 获取操作人信息 operatorID := int64(ctx.SessionUserInfo().Id) operatorName := ctx.SessionUserInfo().NickName // 执行回滚 result, err := h.rollbackSvc.ExecuteRollback(ctx.RequestContext(), orderID, operatorID, operatorName, req.Reason) if err != nil { ctx.AbortWithError(core.Error(http.StatusInternalServerError, 21012, err.Error())) return } rsp := &rollbackOrderResponse{ Success: result.Success, RollbackLogID: result.RollbackLogID, PointsRestored: result.PointsRestored, CouponsRestored: result.CouponsRestored, ItemCardsRestored: result.ItemCardsRestored, InventoryRevoked: result.InventoryRevoked, RefundAmount: result.RefundAmount, ErrorMsg: result.ErrorMsg, } ctx.Payload(rsp) } }