laravel之实现秒杀

介绍

          秒杀多用在电商中,秒杀、抢购、抢票等实现特定需求场景,都可以归为一种资源争用模式,要保证交易的安全性、可靠性,实现方法较多。先看下秒杀的特点、逻辑。

          秒杀特点

  • 抢购人数远多于库存,读写并发巨大
  • 库存少,有效写少
  • 写需强一致性,商品不能超卖
  • 读一致性要求并不高
        秒杀逻辑
          1. 获取秒杀抢购数据信息
          ⒉ . 校验抢购商品的信息,主要商品类型,库存,上下架,时间进行校验
           3.是否已经购买
          4. 更新地址使用时间
           5. sku 的库存
          6. 新建一个订单 ,包含地址,邮编,备注,订单金额,订单类型等信息
           7. 关联用户写入数据
           8. 因为是秒杀商品所以会限制购买数量,只需要一个订单详情即可。
           9.将订单加入到延迟队列中
           10. 返回订单信息
 
 

        下面谈谈laravel组件实现秒杀的方法。

秒杀实现方案

框架环境准备

        我们采用laravels(laravel+swoole)框架,使用rabbitmq消息组件,相关可以参见文章(《laravel之rabbitmq组件使用》https://blog.csdn.net/yan_dk/article/details/117914312

代码集成

 秒杀商品模型类:定义秒杀商品开始时间、结束时间等属性。秒杀商品应该是从商品库取得,通过主键id关联商品表,与商品库应该是1对1关系。后面会通过模型及其关联,逻辑判断商品是否上架,商品秒杀时间等验证规则。

class SeckillProduct extends Model {
    protected $fillable = ['start_at','end_at'];
    protected $dates = ['start_at','end_at'];
    public $timestamps = false;

    public function product()  {
        //关联商品表
        return $this->belongsTo(Product::class);
    }

    public function getIsBeforeStartAttribute()  {
        //判断当前时间是否到了秒杀开始时间之内
//        dd("Carbon::now()==",Carbon::now());
        return Carbon::now()->lt($this->start_at);
    }

    public function getIsAfterEndAttribute()   {
        //判断秒杀时间是否结束
        return Carbon::now()->gt($this->end_at);
    }
}

 

秒杀请求验证类:创建秒杀请求验证类,用于校验秒杀请求或用户输入正确性。
# php artisan make:request Api/V1/SeckillOrderRequest

class SeckillOrderRequest extends FormRequest{

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()   {
      return [
         'address_id' => [
              'required',
             {用户地址是否存在规则}
         ],
         'sku_id' => [
                'required',
             function ($attribute,$value,$fail){
                验证('该商品不存在');
                验证('该商品不支持秒杀');
                验证('该商品不支持秒杀');
                验证('该商品未上架');
                验证('秒杀还未开始');
                验证('秒杀已结束');
                验证('该商品已售完');
                验证('你已经抢购了');
                验证('你已经下单了,请支付');
             }            
         ]
     ]
   }

秒杀控制器类:定义秒杀请求的处理

class OrdersController extends Controller {
public function SeckillOrder(SeckillOrderRequest $request)
    {
        $user = (new UserLoginController())->userinfo()->original[0]->id;
        $address = UserAddr::find($request->input('address_id'));
        $sku = ProductSku::find($request->input('sku_id'));

        $order = \DB::transaction(function () use ($user,$address,$sku){
            $address->update(["last_time" => Carbon::now()]);

            if ($sku->decreaseStock(1) <= 0){
                throw new \RuntimeException("该商品库存不足");
            }

            // 创建一个订单
            $order = new Order([
                'address'      => [ // 将地址信息放入订单中
                    'address'       => $address->full_address,
                    'zip'           => $address->zip,
                ],
                'remark'       => '',
                'total_amount' => $sku->price,
                'type'         => Order::TYPE_SECKILL,
            ]);
            //订单与用户相关联
            $order->user()->associate($user);
            $order->save();

            // 订单关联到当前用户
            $order->user()->associate($user);
            // 写入数据库
            $order->save();
            // 创建一个新的订单项并与 SKU 关联
            $item = $order->items()->make([
                'amount' => 1, // 秒杀商品只能一份
                'price'  => $sku->price,
            ]);
            $item->product()->associate($sku->product_id);
            $item->productSku()->associate($sku);
            $item->save();

            return $order;
        });

        // 秒杀订单的自动关闭时间与普通订单不同
        dispatch(new CloseOrder($order, config('app.seckill_order_ttl')));

        return $order;
    }
}

工作处理类 :工作处理,进入消息队列管理,具体工作是超时后自动关闭订单的相关处理。

生成脚本命令如下

# php artisan make:job CloseOrder

自动生成文件app/Jobs/CloseOrder.php

class CloseOrder implements ShouldQueue{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $order;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Order $order,$time)   {
        $this->order = $order;
        //延迟时间
        $this->delay = $time;
    }

    /**
     * Execute the job.
     * @return void
     */
    public function handle()   {
        if ($this->order->paid_at){
            return;
        }
        \DB::transaction(function (){
            $this->order->update(["closed" => true]);
            foreach ($this->order->items as $item){
                $item->productSku->addStock($item->amount);
            }
        });
    }
}

 

 路由配置

routes/api.php

$api = app('Dingo\Api\Routing\Router');
$api->version('v1',[
    'middleware' => ['bindings'],
    'namespace' => "App\Http\Controllers\Api\V1"
],function ($api){
    $api->get("test","TestController@test")->name("test.test");  //测试连通性
rabbitmq,更新商品详情
    $api->post('SeckillOrder',"OrdersController@SeckillOrder")->name("seckill.order"); //用于测试秒杀
});

相关配置参数

指定秒杀活动开始、结束时间

配置文件修改相关时间参数:config/app.php

'timezone' => 'Asia/Shanghai',
//注意,这里默认 UTC ,要改为Asia/Shanghai
 //增加秒杀模块参数 order_ttl(普通订单延时时间)、seckill_order_ttl(秒杀订单延时时间)
 'order_ttl' => 144000,
 'seckill_order_ttl' => 20,

测试

模拟发起秒杀请求

后台监听消息队列
# php artisan queue:work rabbitmq

调用消息队列中,自动对任务进行处理。

 

Guess you like

Origin blog.csdn.net/yan_dk/article/details/117960504