IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> Laravel8.5+微信小程序实现京东商城秒杀方案 -> 正文阅读

[PHP知识库]Laravel8.5+微信小程序实现京东商城秒杀方案

一、商品秒杀涉及的知识点

  • 鉴权策略封装
  • 掊口访问频次限制
  • 小程序设计
  • 页面防抖
  • 接口调用
  • 订单创建事务使用
  • 超卖防御

二、订单库存系统方案(3种)

扣库存的集中方案

  1. 下单减库存 优点是库存和订单的强一致性,商品不会卖超,但是可能导致恶意下单,影响正常流程
  2. 支付减库存 优点是避免恶意下单,支付和库存强一致性,但是可能出现订单无法支付,商品库存不足等问题。
  3. 预扣库存 预扣库存是指用户请求之后预扣库存,生成订单,在时效内支付,否则订单失效,库存还原

三、小程序秒杀页面

  • 商品秒杀详情页面页面
  • 该商品秒杀时间未到,则上方进行提醒秒杀商品倒计时,并且立即抢购禁用(按钮置灰)

image.png

  • 商品秒杀时间开始,则把立即秒杀按钮禁用状态改为可以点击
  • 点击立即秒杀按钮要考虑页面防抖,不能重复在同一秒中重复发起N次网络请求
    image.png

四、小程序部分代码展示

  1. 小程序wxml代码
<view>商品秒杀页面</view>
<l-countdown time-type="second" time="{{expire_time}}" bind:linend="changeBtn" />
<l-card type="primary" full="{{true}}" image="{{goods.goods.goods_image}}" title="{{goods.goods.goods_name}}">
     <view>
       价格:{{goods.goods.goods_price}}
     </view>
     <view>
     <!-- <button disabled="true" bindtap="buyGoods" >抢购</button> -->
      <l-button disabled="{{ btn_disable }}" bind:lintap="buyGoods" type="error" data-goods_id="{{ goods.goods.id }}">立即秒杀</l-button>
     </view>
  </l-card>
  1. 小程序js代码
// pages/goods_detail/goods_detail.js
import {
  debounce
} from "../../utils/util"

Page({

  /**
   * 页面的初始数据
   */
  data: {
   goods:{},
   expire_time:0,
   btn_disable:false
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (e) {
     //商品id
     let goods_id = e.goods_id;
     this.getGoodsDetail(goods_id)
  },
  //获取商品详情
  getGoodsDetail(goods_id){
    var token = wx.getStorageSync('token')
    wx.request({
      url: 'http://www.zfw.com/api/v1/goods_detail?goods_id='+goods_id,
      header: {
        'Authorization': `Bearer ${token}`
      },
      success: res => {
        //当前的时间戳
        let now_time = Math.round(new Date().getTime() / 1000).toString();
        let expire_time = res.data.data.start_time-now_time;
        this.setData({
          goods:res.data.data,
          expire_time
        })
        if(expire_time > 0){
          this.setData({
            btn_disable:true
          })
        }
        console.log(this.data.goods)
      }
    })
  },
  //立即抢购   debounce此方法是引入的util工具类的页面防抖函数
  buyGoods:debounce(function (e) {
    let goods_id = e[0].currentTarget.dataset.goods_id
    var token = wx.getStorageSync('token')
    wx.request({
      url: 'http://www.zfw.com/api/v1/snap_up',
      header: {
        'Authorization': `Bearer ${token}`
      },
      method:"POST",
      data:{
        goods_id
      },
      success: res => {
        let code = res.statusCode.toString()
        if (!code.startsWith('2')){
           wx.showToast({
             title: '异常!',
             icon:1
           })
        }
        
        if(res.data.errorCode == 0){
            wx.redirectTo({
              url: '/pages/order/order',
            })
        }else{
          wx.showToast({
            title: res.data.msg,
          })
        }
        console.log(res.data)
      }
    })
  }),
  changeBtn(){
    this.setData({
      btn_disable:false
    })
  }
})
  1. 小程序json代码,引入的自定义组件
{
  "usingComponents": {
    "l-card":"/miniprogram_npm/lin-ui/card",
    "l-button":"/miniprogram_npm/lin-ui/button",
    "l-countdown":"/miniprogram_npm/lin-ui/countdown"
  }
}

五、后台业务逻辑

要考虑订单超卖,这次代码使用的redis队列实现的

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Goods;
use Illuminate\Http\Request;
use App\Models\ActivityGoods;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Lcobucci\JWT\Exception;

class GoodsController extends Controller
{
    //商品秒杀列表
    public function activityList()
    {
        $result = ActivityGoods::with(['goods'])
            ->get();
        return response()->json(['errorCode' => 0, 'data' => $result, 'msg' => '查询成功']);
    }

    //商品秒杀列表
    public function goodsDetail(Request $request)
    {
        $goods_id = $request->get('goods_id');
        $result = ActivityGoods::with(['goods'])
            ->where('goods_id',$goods_id)
            ->first();
        return response()->json(['errorCode' => 0, 'data' => $result, 'msg' => '查询成功']);
    }

    //同步库存
    public function syncStock()
    {
        //查出所有参与秒杀活动列表
        $result = ActivityGoods::with(['goods'])
            ->get()->toArray();
        //进行把参与秒杀的商品写入到数据库
        foreach ($result as $val){
            //生成对应商品库存队列
            $goods = "activity_goods_".$val['goods_id'];
            for ($i=0; $i < $val['sku_nums']; $i++) {
                Redis::lpush($goods, 1);
            }
        }
    }
    //校验库存
    public function checkStock(Request $request)
    {
        //获取token
        $token = explode(' ',$request->header('authorization'))[1];
        //进行查看
        $userInfo = Cache::get($token);
        //抢购用户id
        $userID = $userInfo->id;
        //商品id
        $goodsID = $request->input("goods_id");
        //对应商品库存队列
        $goods = "activity_goods_".$goodsID;
        //对应商品抢购成功用户集合 {1,3,4}
        $robSuccessUser = "success_user".$goodsID;
        //进行判断当前用户是否在抢成功的队列里面
        $result = Redis::sismember($robSuccessUser,$userID);
        //如果你在这里面,就抢完了
        if ($result) {
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['errorCode' => 20000, 'data' => '', 'msg' => '已经抢购过了']);
        }
        //减库存,把队列里面的数据从左边 头
        $count = Redis::lpop($goods);
        if (!$count) {
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['errorCode' => 20001, 'data' => '', 'msg' => '已经抢光了哦']);
        }
        //把当前这个秒杀的uid存储到中奖的队列里set
        $success = Redis::sadd($robSuccessUser, $userID);
        if(!$success){
            //已经在成功队列里了,加回库存,防止的是同个用户并发请求
            Redis::lpush($goods, 1);
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['errorCode' => 20002, 'data' => '', 'msg' => '已经抢购过了']);
        }

        //如果抢购成功 返回状态码,进行下单
        return response()->json(['errorCode' => 0, 'data' => '', 'msg' => '秒杀成功']);
    }


    //创建订单
    public function createOrder(Request $request)
    {


        //获取token
        $token = explode(' ',$request->header('authorization'))[1];
        //进行查看
        $userInfo = Cache::get($token);
        //抢购用户id
        $userID = $userInfo->id;

        //商品id
        $goodsID = $request->input("goods_id");
        //对应商品抢购成功用户集合 {1,3,4}
        $robSuccessUser = "success_user".$goodsID;
        //进行判断当前用户是否在抢成功的队列里面
        $result = Redis::sismember($robSuccessUser,$userID);
        //如果你在这里面,就抢完了
        if (!$result) {
            //如果抢购成功 返回状态码,进行下单
            return response()->json(['errorCode' => 20003, 'data' => '', 'msg' => '手慢了!']);
        }

        DB::beginTransaction();
        try{
            //减库存

            //生成订单


            DB::commit();
            //下单成功,跳转支付页面
            return response()->json(['errorCode' => 0, 'data' => '', 'msg' => '下单成功!']);
        }catch (\Exception $e){
            DB::rollBack();
        }

    }
}

六、END

免费获取获取小程序前台及后台框架代码以及数据库sql文件:可以微信搜一搜「 柯作 」并关注,回复【秒杀】

或者直接扫码关注微信公众号

公众号图片.jpg

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-07-10 14:19:19  更:2021-07-10 14:20:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 10:58:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码