訂單是我們在日常開發中經常會遇到的一個功能,最近在做一個訂單過期與超時的開發。訂單過期與超時就不用我解釋了吧,其實兩者都是同一個問題來著,就是訂單未支付的處理,我們要做的是對這些未支付的訂單到了一定時間就自動取消,好了,你第一反應那肯定就是做一個定時任務了!是的,就是定時任務,但是哪個才會是最佳方案 ...
訂單是我們在日常開發中經常會遇到的一個功能,最近在做一個訂單過期與超時的開發。訂單過期與超時就不用我解釋了吧,其實兩者都是同一個問題來著,就是訂單未支付的處理,我們要做的是對這些未支付的訂單到了一定時間就自動取消,好了,你第一反應那肯定就是做一個定時任務了!是的,就是定時任務,但是哪個才會是最佳方案呢,下麵來看看:
一、前端到時間請求取消
你肯定不會想著在前端來做定時請求取消該訂單的,因為如果客戶禁用了該應用或者沒聯網,那到了一定時間時,還一直都是未處理狀態的。所以前端一般都是手動去觸發取消訂單的。
二、服務端定時查詢有沒有需要取消的訂單,然後批量處理。
這種方法我們用得最多的,不過存在準確度的問題,還有需要確認定時任務的周期,但是我們可以結合redis來實現,我們可以在存redis時候順便存在mysql這樣的資料庫做長久存儲然後用服務端定時查詢,這裡我用到了redis的訂閱功能。
首先先修改一下配置
然後再重啟redis
這裡創建4個文件
index.php 創建訂單,發佈消息,10s後查詢訂單狀態並更新訂單
<?php
require_once 'Redis2.class.php';
require_once 'db.class.php';
$redis2 = new \Redis2();
$list = $redis2->lRange('order',0,9999999);
$mysql = new \mysql();
$mysql->connect();
$data = ['ordersn'=>'SN'.time().'T'.rand(10000000,99999999),'status'=>0,'createtime'=>date('Y-m-d H:i:s',time())];
$mysql->insert('order',$data);
$order_sn = $data['ordersn'];
$redis2->setex($order_sn,10,$order_sn);
psubscribe.php,發佈redis訂閱的一些邏輯處理
這裡我把redis的一些操作和mysql的一些處理做了封裝處理,這個你們可以用到你們自己的項目當中的
Redis2.class.php
<?php
class Redis2
{
private $redis;
public function __construct($host = '127.0.0.1', $port = 6379)
{
$this->redis = new Redis();
$this->redis->connect($host, $port);
$this->redis->auth('123456');
}
public function setex($key, $time, $val)
{
return $this->redis->setex($key, $time, $val);
}
public function set($key, $val)
{
return $this->redis->set($key, $val);
}
public function get($key)
{
return $this->redis->get($key);
}
public function expire($key = null, $time = 0)
{
return $this->redis->expire($key, $time);
}
public function psubscribe($patterns = array(), $callback)
{
$this->redis->psubscribe($patterns, $callback);
}
public function setOption()
{
$this->redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
}
public function lRange($key,$start,$end)
{
return $this->redis->lRange($key,$start,$end);
}
public function lPush($key, $value1, $value2 = null, $valueN = null ){
return $this->redis->lPush($key, $value1, $value2 = null, $valueN = null );
}
public function delete($key1, $key2 = null, $key3 = null)
{
return $this->redis->delete($key1, $key2 = null, $key3 = null);
}
}
db.class.php,對sql處理的一些封裝
<?php
class mysql
{
private $mysqli;
private $result;
/**
* 資料庫連接
* @param $config 配置數組
*/
public function connect()
{
$config=array(
'host'=>'127.0.0.1',
'username'=>'root',
'password'=>'123456qwerty',
'database'=>'marhal',
'port'=>3306,
);
$host = $config['host']; //主機地址
$username = $config['username'];//用戶名
$password = $config['password'];//密碼
$database = $config['database'];//資料庫
$port = $config['port']; //埠號
$this->mysqli = new mysqli($host, $username, $password, $database, $port);
}
/**
* 數據查詢
* @param $table 數據表
* @param null $field 欄位
* @param null $where 條件
* @return mixed 查詢結果數目
*/
public function select($table, $field = null, $where = null)
{
$sql = "SELECT * FROM `{$table}`";
//echo $sql;exit;
if (!empty($field)) {
$field = '`' . implode('`,`', $field) . '`';
$sql = str_replace('*', $field, $sql);
}
if (!empty($where)) {
$sql = $sql . ' WHERE ' . $where;
}
$this->result = $this->mysqli->query($sql);
return $this->result;
}
/**
* @return mixed 獲取全部結果
*/
public function fetchAll()
{
return $this->result->fetch_all(MYSQLI_ASSOC);
}
/**
* 插入數據
* @param $table 數據表
* @param $data 數據數組
* @return mixed 插入ID
*/
public function insert($table, $data)
{
foreach ($data as $key => $value) {
$data[$key] = $this->mysqli->real_escape_string($value);
}
$keys = '`' . implode('`,`', array_keys($data)) . '`';
$values = '\'' . implode("','", array_values($data)) . '\'';
$sql = "INSERT INTO `{$table}`( {$keys} )VALUES( {$values} )";
$this->mysqli->query($sql);
return $this->mysqli->insert_id;
}
/**
* 更新數據
* @param $table 數據表
* @param $data 數據數組
* @param $where 過濾條件
* @return mixed 受影響記錄
*/
public function update($table, $data, $where)
{
foreach ($data as $key => $value) {
$data[$key] = $this->mysqli->real_escape_string($value);
}
$sets = array();
foreach ($data as $key => $value) {
$kstr = '`' . $key . '`';
$vstr = '\'' . $value . '\'';
array_push($sets, $kstr . '=' . $vstr);
}
$kav = implode(',', $sets);
$sql = "UPDATE `{$table}` SET {$kav} WHERE {$where}";
$this->mysqli->query($sql);
return $this->mysqli->affected_rows;
}
/**
* 刪除數據
* @param $table 數據表
* @param $where 過濾條件
* @return mixed 受影響記錄
*/
public function delete($table, $where)
{
$sql = "DELETE FROM `{$table}` WHERE {$where}";
$this->mysqli->query($sql);
return $this->mysqli->affected_rows;
}
}
對每一次訂單的訪問我們做了伺服器監聽任務,如下:
設置本地功能變數名稱並訪問http://www.startphp.cn/index.php
此時,每訪問一次index.php,就會創建一個訂單,10s鐘後,如果該訂單依舊處於未支付狀態,就設置訂單失效。
但是php的cli模式在伺服器運行後,總是會掉線,處理這個問題的方法,我們不妨寫一個腳本
1.編寫shell腳本,定時檢查進程是否存在,不存在的話就開啟服務,並且將運行情況寫入日誌
腳本文件如下:
#!/bin/sh
PIDS=`pidof php`
if [ "$PIDS" != "" ]; then
echo "在運行"
echo -e $(date +%Y"."%m"."%d" "%k":"%M":"%S)" running....." >> /mytask/task.run.log
else
echo "不在運行,開始啟動"
echo -e $(date +%Y"."%m"."%d" "%k":"%M":"%S)" start php start....." >> /mytask/task.start.log
cd /var/www/html/redis
php psubscribe.php &
echo -e $(date +%Y"."%m"."%d" "%k":"%M":"%S)" start php success....." >> /mytask/task.start.log
fi
在crontab任務里創建任務,這裡設定的是每5s檢查一次,crontab -e
效果你可以查看task.run.log
結果如下:
有需要學習交流的友人請加入交流群的咱們一起,群內都是1-7年的開發者,希望可以一起交流,探討PHP,swoole這塊的技術 或者有其他問題 也可以問,獲取swoole或者php進階相關資料私聊管理即可
別忘了點贊哦,定期分享乾貨