对于这个需求,我以前写过Laravel
版本的。今天想在TP5.1
中实现这个功能,但是网上基本没什么教程可供参考,所以写篇文章仅供大家学习。
一、前台
1、先来加载订单确认页面
当下单成功后,通过js
跳转到订单确认页,在checkout.html
中:
{block name="js"}<script>$(function () {//去付款,就是下单,跳到订单确认页$("#pay").click(function () {var address_id = $(".address").data('id');if (address_id == '') {alert('请先填写一个送货地址~');return false;}$.ajax({type: 'POST',url: '/index/Order/store',success: function (data) {if (data.status == 0) {alert(data.info);location.href = '/index/Cart/index';return false;}//微信支付location.href = '/index/Order/pay/id/' + data.order_id;}})});});</script>{/block}
2、在Order.php
中增加对应方法,代码如下:
/**** 订单确认页* @param $id*/public function pay($id){$order = \app\common\model\Order::with('address')->find($id);return view('order/show_pay', compact('order'));}
3、修改show_pay.html
页面代码:
{extend name="layout/app" /}{block name="content"}<div id="wrapper"><div class="page-order-pay" data-log="在线支付"><div class="box box1"><div class="p1"><span class="icon-checked"></span><span>订单提交成功</span></div><div class="p2"><span style="color: #FF5722">请在30分钟内完成支付,超时订单将自动关闭。</span></div><div class="p2"><p class="count" style="color: #D92E2E"></p></div></div><div class="box box2"><div class="p">订单金额:{$order->total_price}元 订单编号:{$order->out_trade_no}</div><div class="p h_box"><div>收货信息:</div><div class="flex_1">{$order->address->name} {$order->address->tel}<br>{$order->address->province} {$order->address->city} {$order->address->area} {$order->address->detail}</div></div><div class="p">发票类型:个人电子发票 <p>发票抬头:个人</p></div></div><div class="box box3"><div class="head"><span>请选择支付方式</span></div><div class="list"><div class="item active"><div data-log="A0-支付宝" class="inner"><div class="p">微信</div><div class="p right">大额支付推荐使用微信快捷支付</div></div></div></div></div><div class="box box4"><div class="p p1"><p>本次需支付:<span class="hot">{$order->total_price}元</span></p></div></div><div class="box box5"><a href="javascript:;" data-log="bottom-bankgo" class="ui-button"><span>立即支付</span></a></div></div></div>{/block}{block name="js"}<script>$(function () {window.onload = function () {countDown();function addZero(i) {return i < 10 ? "0" + i : i + "";}function countDown() {var nowtime = new Date();var endtime = new Date({:json_encode($order->create_time)})endtime.setSeconds(endtime.getSeconds() + 30); // 设置30秒// endtime.setMinutes(endtime.getMinutes() + 30); // 设置30分钟var lefttime = parseInt(endtime.getTime() - nowtime.getTime()) / 1000;var m = parseInt(lefttime / 60 % 60);var s = parseInt(lefttime % 60);m = addZero(m);s = addZero(s);document.querySelector(".count").innerHTML = `剩余支付时间:${m} 分 ${s} 秒`;if (lefttime <= 0) {document.querySelector(".count").innerHTML = "订单已失效";return;}setTimeout(countDown, 1000);}}})</script>{/block}
上面的这段js
参考文章 /LightLinV/article/details/88602302
这里js里面使用 {:json_encode($order->create_time)} 来解析读取订单创建的时间,其实就是把控制器传过来的 $order->create_time 变量变成一个 JSON 字符串,赋值给 JS 的 endtime 变量。
此时,正常步骤下单,出现的页面样式如下:
现在我们看到的时间其实是一个假的时间显示,并不能实现真正的功能。接下来,我们来实现超时未支付关闭订单的功能。
首先我们在orders
表中增加一个字段closed
,TINYINT
类型,长度1
,默认值1
。其中1
代表正常订单,0
代表失效订单。
然后执行下面的命令安装消息队列,参考文档:/top-think/think-queue
composer require topthink/think-queue 2.0.4
注意:如果是TP6版本请去掉后面的版本号,TP5.1只能安装上面的包。
此举会在项目的config
文件夹里创建queue.php
文件,修改该文件配置如下:
return ['REDIS_CLIENT' => 'predis','REDIS_HOST' => '127.0.0.1','connector' => 'redis' // 这里默认是sync,是一个同步操作,我需要任务延迟执行,所以配置的redis,当然也可用database];
二、后台
1、我们手动来创建一个任务,在application/job/CloseOrder.php
创建任务文件,在CloseOrder.php
中添加代码:
<?phpnamespace app\job;use think\queue\Job;use app\common\model\Order;class CloseOrder{public function fire(Job $job, $data){$order = Order::where('out_trade_no', $data['out_trade_no'])->find();// 如果已经支付则不需要关闭订单,直接退出if ($order->pay_time) {return;}Order::where('id', $order->id)->update(['closed' => false]);// 循环遍历订单中的商品,将订单中的数量加回到商品的库存中去foreach ($order->orderProducts as $item) {$item->product->addStock($item->num);}if ($job->attempts() > 3) {//通过这个方法可以检查这个任务已经重试了几次了return;}//如果任务执行成功后 记得删除任务,不然这个任务会重复执行,直到达到最大重试次数后失败后,执行failed方法$job->delete();// 也可以重新发布这个任务$job->release(env('order_ttl')); //$delay为延迟时间}public function failed($data){// ...任务达到最大重试次数后,失败了}}
然后在app\common\model\Product
商品模型里面新增方法:
/**** 增加库存* @param $num* @return int*/public function addStock($num){return $this->setInc('stock', $num);}
这个方法是用于延迟队列任务执行成功后,退回对应库存的
在app\common\model\OrderProduct.php
中添加代码如下:
public function product(){return $this->belongsTo('Product', 'product_id');}
2、触发任务
当事务提交成功后,执行任务处理器。在app/index/controller/Order.php
控制器的store
方法中,添加如下代码:
use think\Queue;use app\job\CloseOrder;...// 提交事务Db::commit();Queue::later(env('order_ttl'), CloseOrder::class, $data = $order, $queue = null);
上面的order_ttl是延迟时间,我们在.env文件中,配置时间为30,代表30秒。其他参数解释,请参考上面消息队列的文档,已经解释的很清楚了。
.env
配置如下:
order_ttl=30
接下来安装redis
!!!这里说明一下,根据自己电脑环境的
PHP版本来做对应安装,若之前已安装过下面的3、4步,请忽略!!!
3、安装Redis
服务
brew install redis#Mac电脑直接全局安装
启动服务
redis-server
成功后,你会看到如下图所示:
我的电脑目前安装的PHP
版本是7.4.5
php -v #查看版本 PHP 7.4.5 (cli)which php #查看PHP本地路径 /usr/local/bin/php
4、安装php-redis
扩展,参考地址:/package/redis/。
这里我下载的是redis-5.0.2
, 点击右边的版本号即可下载,下载后在文件目录依次执行下面的命令解压编译:
tar xzf redis-5.0.2.tgz #解压cd redis-5.0.2phpize ./configure --with-php-config=/usr/local/bin/php-config # 这里的路径请换成自己的make install #编译安装
接下来在已启用的php
配置文件php.ini
启用redis
,路径视安装路径为准,我的路径为:/usr/local/etc/php/php.ini
.在配置文件中添加:
extension=redis.so;
检查redis
扩展是否成功安装,有redis
字样输出即是成功安装
php -m | grep redis
5、由于在config/queue.php
文件中配置了predis
,所以执行最后一步安装:
composer require predis/predis
6、监听任务并执行
php think queue:listenphp think queue:work
7、测试
进入商品表,任意选择一个商品并将其加入购物车,记住库存数量,提交订单。
经测试,发现下单后,对应商品的库存减少了,30
秒后,当任务执行成功,发现订单表的closed
字段有原来的1
修改成了0
,并且库存还原。