zoukankan      html  css  js  c++  java
  • 30 分钟未付款取消订单,怎么做?

    点击上方“ 码农编程进阶笔记 ”,选择“置顶或者星标

    文末有干货,每天定时与您相约!

    第一次亲密接触

    问题:我这边有个需求,用户下单后 30 分钟如果没付款就取消掉,这个要怎么写呀。

    qufo: 这个还不简单,写个取消订单的命令,弄个计划任务定时不就行了。

    舞飞杨:哦,就是 crontab ?

    qufo: 是呀,follow me

    先来个

    $php artisan make:command OrderCancel
    Console command created successfully.
    

    然后修改 app\Console\Commands\OrderCancel.php 为如下:

    <?php
    namespace App\Console\Commands;
    use App\Http\Models\Order;
    use Illuminate\Console\Command;
    
    
    class OrderCancel extends Command
    {
        /**
         * The name and signature of the console command.
         *
         * @var string
         */
        protected $signature = 'order:cancel';
        /**
         * The console command description.
         *
         * @var string
         */
        protected $description = '30分钟未付款取消订单';
    
    
        /**
         * Create a new command instance.
         *
         * @return void
         */
        public function __construct()
    {
            parent::__construct();
        }
    
    
    
    
        /**
         * Execute the console command.
         *
         * @return mixed
         * @throws
         */
        public function handle()
    {
            try {
                $unPaid = Order::where('created','<',time()-30*60) //创建时间在30分钟以前
                ->where('order_status',1) // 刚下单未支付
                ->get();
                foreach ($unPaid as $order) {
                    $order->cancel(); // 执行取消动作
                }
            } catch (\Exception $e) {
                throw $e;
            }
            return true;
        }
    }
    

    试一下在项目根下执行 php artisan list 应该能看到下面那一行了。

    order
    order:cancel   30分钟未付款取消订单
    

    直接执行命令 php artisan order:cancel 即可测试本地取消订单。

    执行系统命令 crontab -e , 在里面加入

    * * * * * cd /项目的根目录 && php artisan schedule:run >> /dev/null 2>&1
    

    然后 app\Console\Kernel.php 的 schedule 方法里,加入下面一行:

    $schedule->command('order:cancel')->everyMinute();
    

    这样,取消订单就会每分钟自动执行一次了,省事了。

    代志大条了

    舞飞杨:上次那个计划任务真不错,可以自动执行,可是近来订单增多,经常是前一个任务还没执行完下一个任务又开始启动了,然后锁着表改不了数据更惨了。

    qufo: 那是,业务量小的时候这个方案好用方便,可是业务量大了,重入会出问题;而且定时任务涉及到 crontab 的权限控制问题。订单量大一点就不好用了。而且,因为我们的任务每分钟执行一次,所以有些订单会在 30 分钟的时候执行取消,有些会在接近 31 分的时候执行。就算没订单,一天也重复执行 1440 次。随着业务的扩展,除了取消订单,还会有提醒支付,催商家发货,催用户确认收货,催骑手接单等等一堆事情,这些加进去,计划任务越来越庸肿,执行效率大大降低,搞不好容易出大事。

    舞飞杨:对呀对呀,现在的计划任务已经有 20 多个了,再加进去不是办法呀。之前的任务现在执行得乱 78 糟,全乱套了。现在还有什么好办法么?

    qufo:有倒是有,不过我需要你有用过一样东西。

    舞飞杨:你要什么?流氓。

    qufo: 什么流氓,我说要用 redis 。

    舞飞杨:哦,我知道,我装过,用过一阵子,不过,这有什么关系?

    qufo:在订单确认成功之后,往 redis 里加入 key, 用 ORDER_CONFIRM:订单ID 这样的格式来,然后定义他 30 分钟后过期,我们监听这个键过期事件就好了。

    先保证 redis 的版本大于 2.8 ,现在绝大部分不成问题了,然后修改 redis 的配置文件,加入

    notify-keyspace-events "Ex"
    

    以启用键过期的通知。

    然后重新启动 redis 。

    在 .env 里,确认 CACHE_DRIVER=redis ,并配置好相应的服务地址,密码之类的。

    然后,在控制器中,处理好订单确认写入数据库后,增加一行

    // 30分钟后过期--执行取消订单
    Cache::store('redis')->put('ORDER_CONFIRM:'.$order->id,$order->id,30);
    

    然后我们来监听 ORDER_CONFIRM:ORDER_ID 的过期事件

    先建个命令,我们一会儿的监听全靠他了。

    $php artisan make:command OrderExpireListen
    Console command created successfully.
    

    然后把命令执行文件 app\Console\Commands\OrderExpireListen.php 写成这样:

    <?php
    namespace App\Console\Commands;
    
    
    use App\Http\Models\Order;
    use Illuminate\Console\Command;
    use Illuminate\Support\Facades\Redis as Redis;
    
    
    class OrderExpireListen extends Command
    {
        /**
         * The name and signature of the console command.
         *
         * @var string
         */
        protected $signature = 'order:expire';
    
    
    
    
        /**
         * The console command description.
         *
         * @var string
         */
        protected $description = '监听订单创建,在30分钟后如果没付款取消订单。';
    
    
    
    
        /**
         * Create a new command instance.
         *
         * @return void
         */
        public function __construct()
    {
            parent::__construct();
        }
    
    
        /**
         * Execute the console command.
         *
         * @return mixed
         */
        public function handle()
    {
            //
            $cachedb = config('database.redis.cache.database',0);
            $pattern = '__keyevent@'.$cachedb.'__:expired';
            Redis::subscribe([$pattern],function ($channel){     // 订阅键过期事件
                $key_type = str_before($channel,':');
                switch ($key_type) {
                    case 'ORDER_CONFIRM':
                        $order_id = str_after($channel,':');    // 取出订单 ID
                        $order = Order::find($order_id);
                        if ($order) {
                            $order->cancel(); // 执行取消操作
                        }
                        break;
                    case 'ORDER_OTHEREVENT':
                        break;
                    default:
                        break;
                }
            });
        }
    }
    

    文件好了之后,使用

    $php artisan order:expire
    

    启动,就可以了。

    redis 的默认连接是有超时的。

    你改下 app\config\database.php 中 redis 节,增加一个 read_write_timeout :

    'redis' => [
            'client' => 'predis',
            'default' => [
                'host' => env('REDIS_HOST', '127.0.0.1'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => env('REDIS_DB', 0),
                'read_write_timeout' => env('REDIS_RW_TIMEOUT', 5),  // 读写超时设定
            ],
        ],
    

    然后在 .env 中配置 REDIS_RW_TIMEOUT=-1 这样就不会超时了。

    舞飞杨:小哥哥,上次的东西真好,我把计划任务全改成那个了,好用,而且时间准,互不影响,

    qufo: 嗯。

    舞飞杨:可是我们的业务增长很快,一台机器处理不了,已经组了应用群集了,每台机器上都要装 redis 吗?

    qufo: 嗯。

    舞飞杨:不是吧,那么多 redis 服务器一台一个,能集中处理吗?所有的应用都把键存到一台机器上,然后只要一份监听程序监听那个过期事件?

    qufo: 嗯。

    舞飞杨:我听说你很厉害才找你。要是一台监听处理的机器处理来不及,再加一台去处理吗?

    qufo: 嗯。

    舞飞杨:嗯什么嗯,是你不知道吧?!

    qufo: 什么叫不知道,当业务量大起来的时候,直接增加监听处理的机器是不行的,他们监听同一个过期事件,两台机器会同时接到过期事件,除非进行 hash 分工,要不然处理两遍事件就傻了。业务量足够大的时候,得用消息队列了。

    舞飞杨:哦,消息队列怎么用?

    qufo: 上次的监听处理程序只要一台处理,把监听处理的过程改一下,取出订单 ID 之后不要去处理,通过 rpush 放到一个 redis 的队列里去。另外起几台服务器,连到这个 redis 服务器,通过 blpop 接收消息队列里出来的订单 ID。这样,多台机器可以同时工作,一个订单只会从 blpop 里出来一次,不会重复执行,多台机器可以分担任务,又互不影响。消息队列也可以换成业界成熟的 rabbitmq 、 kafka 之类的专业消息队列,那又是另外一个话题了。反正业务量大了,变复杂了,消息总线跑不掉,天猫京东也差不多如此。

    链接:https://learnku.com/articles/21488

  • 相关阅读:
    Linux 学习 step by step (1)
    ubuntu server nginx 安装与配置
    ubuntu server samba服务器配置
    iOS app集成支付宝支付流程及后台php订单签名处理
    mac 连接windows 共享内容
    linux 文件查找,which,whereis,locate,find
    ubuntu server vsftpd 虚拟用户及目录
    ubuntu server 安装 mantis bug tracker 中文配置
    ubuntu server vsftpd 匿名用户上传下载及目录设置
    linux 用户管理,用户权限管理,用户组管理
  • 原文地址:https://www.cnblogs.com/lxwphp/p/15452855.html
Copyright © 2011-2022 走看看