getSeckill($seckill); // 从 Redis 获取秒杀活动数据 $product = $redisSeckill->product; // 获取秒杀商品信息 /** * @var $user User * 如果用户已登录,则返回该用户的所有地址列表;否则返回一个空集合 */ $addresses = collect(); // 初始化地址集合 if ($user = auth()->user()) { $addresses = $user->addresses()->get(); // 获取用户地址 } return view('seckills.show', compact('redisSeckill', 'product', 'addresses')); // 返回视图 } /** * 抢购秒杀商品 * * @param Request $request 请求对象 * @param int $id 秒杀活动的 ID * @return \Illuminate\Http\JsonResponse * @throws \Exception */ public function storeSeckill(Request $request, $id) { /** * 直接从 session 中读取用户 ID,不经过数据库 * * @var $user User * @var $auth SessionGuard */ $seckill = new Seckill(compact('id')); // 创建秒杀活动实例 $auth = auth('web'); // 获取当前会话 $userId = session()->get($auth->getName()); // 获取用户 ID try { // 检查请求中是否包含地址 ID if (!$request->has('address_id')) { throw new \Exception('必须选择一个地址'); // 抛出异常 } // 验证秒杀活动是否存在及是否已开始 $redisSeckill = $this->redisSeckill = $this->getSeckill($seckill); if (!$redisSeckill->is_start) { throw new \Exception('秒杀未开始'); // 抛出异常 } } catch (\Exception $e) { return responseJson(402, $e->getMessage()); // 返回错误响应 } // 开始抢购逻辑,检查队列中是否还有可抢购的商品 if (is_null(Redis::lpop($seckill->getRedisQueueKey()))) { return responseJson(403, '已经抢购完了'); // 返回错误响应 } DB::beginTransaction(); // 开始数据库事务 try { $product = $redisSeckill->product; // 获取秒杀商品 if (is_null($product)) { return responseJson(400, '商品已下架'); // 返回错误响应 } // 验证用户的收货地址是否有效 $user = auth()->user(); $address = Address::query()->where('user_id', $user->id)->find($request->input('address_id')); if (is_null($address)) { return responseJson(400, '无效的收货地址'); // 返回错误响应 } // 创建秒杀主表订单和明细表订单,默认数量为 1 $masterOrder = ($orderUtil = new OrderUtil([['product' => $product]]))->make($user->id, $address); $masterOrder->type = OrderTypeEnum::SEC_KILL; // 设置订单类型为秒杀 $masterOrder->amount = $redisSeckill->price; // 设置订单金额 $masterOrder->save(); // 保存订单 // 创建订单明细 $details = $orderUtil->getDetails(); data_set($details, '*.order_id', $masterOrder->id); // 设置订单 ID OrderDetail::query()->insert($details); // 保存订单明细 // 设置未支付订单的自动取消时间 $setting = new SettingKeyEnum(SettingKeyEnum::UN_PAY_CANCEL_TIME); $delay = Carbon::now()->addMinute(setting($setting, 30)); // 设置延迟时间 CancelUnPayOrder::dispatch($masterOrder)->delay($delay); // 调度取消任务 // 生成支付信息 $form = $this->buildPayForm($masterOrder, (new Agent)->isMobile())->getContent(); } catch (\Exception $e) { DB::rollBack(); // 回滚事务 // 回滚秒杀数量 Redis::lpush($seckill->getRedisQueueKey(), 9); // 从 Redis 中删除当前用户的抢购记录,允许用户重新抢购 Redis::del($seckill->getUsersKey($userId)); return responseJson(403, $e->getMessage()); // 返回错误响应 } DB::commit(); // 提交事务 // 更新秒杀活动的剩余数量 $redisSeckill->sale_count += 1; // 增加销售数量 $redisSeckill->number -= 1; // 减少库存数量 Redis::set($seckill->getRedisModelKey(), json_encode($redisSeckill)); // 更新 Redis 中的秒杀活动数据 // 存储抢购成功的用户名 $user = auth()->user(); Redis::hset($seckill->getUsersKey($userId), 'name', $user->hidden_name); return responseJson(200, '抢购成功', compact('form')); // 返回成功响应 } /** * 获取参与秒杀活动的用户 * * @param int $id 秒杀活动的 ID * @return \Illuminate\Http\JsonResponse */ public function getSeckillUsers($id) { $seckill = new Seckill(compact('id')); // 创建秒杀活动实例 $keys = Redis::keys($seckill->getUsersKey('*')); // 获取所有参与用户的键 $users = collect(); // 初始化用户集合 foreach ($keys as $key) { $users->push(Redis::hget($key, 'name')); // 获取用户名称并添加到集合 } return responseJson(200, 'success', $users); // 返回成功响应 } /** * 从 Redis 中获取秒杀活动的数据 * * @param Seckill $seckill 秒杀活动实例 * @return mixed */ protected function getSeckill(Seckill $seckill) { /** * @var $product Product */ $json = Redis::get($seckill->getRedisModelKey()); // 从 Redis 获取秒杀活动数据 $redisSeckill = json_decode($json); // 解析 JSON 数据 if (is_null($redisSeckill)) { abort(403, "没有这个秒杀活动"); // 返回 403 错误 } // 获取当前时间和秒杀结束时间 $now = Carbon::now(); $endAt = Carbon::make($redisSeckill->end_at); if ($now->gt($endAt)) { abort(403, "秒杀已经结束"); // 返回 403 错误 } // 检查秒杀是否已经开始 $startAt = Carbon::make($redisSeckill->start_at); $redisSeckill->is_start = $now->gt($startAt); // 设置活动是否开始 // 计算倒计时 $redisSeckill->diff_time = $startAt->getTimestamp() - time(); return $redisSeckill; // 返回秒杀活动数据 } /** * 重写父类的构建订单明细 * * @param Product $product 产品实例 * @param int $number 数量 * @return array 订单明细数组 */ protected function buildOrderDetail(Product $product, $number) { $attribute = [ 'product_id' => $product->id, // 设置产品 ID 'number' => $number // 设置数量 ]; // 价格为秒杀的价格,直接从 Redis 中读取 $attribute['price'] = ceilTwoPrice($this->redisSeckill->price); // 设置价格 $attribute['total'] = ceilTwoPrice($attribute['price'] * $attribute['number']); // 计算总价 return $attribute; // 返回订单明细数组 } }