You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aquaculture/app/Http/Controllers/User/SeckillController.php

236 lines
6.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Http\Controllers\User;
use App\Enums\OrderTypeEnum;
use App\Enums\SettingKeyEnum;
use App\Jobs\CancelUnPayOrder;
use App\Models\Address;
use App\Models\Order;
use App\Models\OrderDetail;
use App\Models\Product;
use App\Models\Seckill;
use App\Models\User;
use App\Utils\OrderUtil;
use Carbon\Carbon;
use Illuminate\Auth\SessionGuard;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Jenssegers\Agent\Agent;
class SeckillController extends PaymentController
{
protected $redisSeckill;
public function show($id)
{
$seckill = new Seckill(compact('id'));
$redisSeckill = $this->getSeckill($seckill);
$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 $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());
try {
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());
}
// // 返回 0代表之前已经设置过了代表已经抢过
// if (0 == Redis::hset($seckill->getUsersKey($userId), 'id', $userId)) {
//
// return responseJson(403, '你已经抢购过了');
// }
// 开始抢购逻辑,如果从队列中读取不到了,代表已经抢购完成
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, '无效的收货地址');
}
// 创建一个秒杀主表订单和明细表订单,默认数量一个
$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);
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::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));
// 存储抢购成功的用户名
$user = auth()->user();
Redis::hset($seckill->getUsersKey($userId), 'name', $user->hidden_name);
return responseJson(200, '抢购成功', compact('form'));
}
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());
$redisSeckill = json_decode($json);
if (is_null($redisSeckill)) {
abort(403, "没有这个秒杀活动");
}
// 得到这些时间
$now = Carbon::now();
$endAt = Carbon::make($redisSeckill->end_at);
if ($now->gt($endAt)) {
abort(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 $number
* @return array
*/
protected function buildOrderDetail(Product $product, $number)
{
$attribute = [
'product_id' => $product->id,
'number' => $number
];
// 价格为秒杀的价格, 直接从 redis 中读取
$attribute['price'] = ceilTwoPrice($this->redisSeckill->price);
$attribute['total'] = ceilTwoPrice($attribute['price'] * $attribute['number']);
return $attribute;
}
}