訂單是我們?cè)谌粘i_發(fā)中經(jīng)常會(huì)遇到的一個(gè)功能辐真,最近在做業(yè)務(wù)的時(shí)候需要實(shí)現(xiàn)客戶下單之后訂單超時(shí)未支付自動(dòng)取消的功能,剛開始確認(rèn)了幾種方法:
- 客戶端到時(shí)間請(qǐng)求取消
- 服務(wù)端定時(shí)查詢有沒有需要取消的訂單,然后批量處理
- 下單后創(chuàng)建定時(shí)器适滓,延時(shí)處理
- 使用redis或者memcache存儲(chǔ)仆邓,設(shè)置過期時(shí)間鳖擒,自動(dòng)刪除
上面的方法相信很多人都很熟悉了绳匀,雖然不熟悉也看過很多它的知識(shí)了吧芋忿,所以就不多廢話了炸客。今天要說的是第三種方法疾棵,為了防止進(jìn)程進(jìn)入阻塞,所以需要借助Swoole痹仙,workerman這樣的框架來開啟異步的定時(shí)器是尔,今天講解的是如何利用Swoole來處理訂單過期時(shí)間。
環(huán)境要求
- 安裝swoole擴(kuò)展 安裝請(qǐng)參考swoole官網(wǎng)里面有詳細(xì)的安裝教程
- 配置php.ini開啟proc_open
swoole一次性定時(shí)器swoole_timer_after
官網(wǎng)介紹如下:
woole_timer_after函數(shù)是一個(gè)一次性定時(shí)器开仰,執(zhí)行完成后就會(huì)銷毀拟枚。此函數(shù)與PHP標(biāo)準(zhǔn)庫(kù)提供的sleep函數(shù)不同薪铜,after是非阻塞的。而sleep調(diào)用后會(huì)導(dǎo)致當(dāng)前的進(jìn)程進(jìn)入阻塞恩溅,將無法處理新的請(qǐng)求隔箍。
執(zhí)行成功返回定時(shí)器ID,若取消定時(shí)器蜀铲,可調(diào)用 swoole_timer_clear
實(shí)現(xiàn)邏輯
完整代碼
開啟定時(shí)器
/*
*開啟定時(shí)器
*id 訂單id
*/
public function openTimer($id)
{
$arr = ['order_id' => $id];
$json = base64_encode(json_encode($arr));
//處理訂單過期請(qǐng)求的地址
$path = Config::get('order.past_order_url');
// 定時(shí)器腳本路徑
$address = Config::get('order.timer_url');
$cmd = "php Timer.php -a 1 -u {$path} -p {$json} -t 1800000";
$descriptorspec = array(
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["file", "{$address}/error.txt", "a"]
);
proc_open($cmd, $descriptorspec, $pipes, $address, NULL);
return true;
}
Timer.php
class Timer
{
public $request_url = null;
public $params = [];
public $time = 1000;
public function __construct()
{
$param_arr = getopt('a:u:p:t:');
if (!isset($param_arr['a'])) {
$this->writeLog('【傳參錯(cuò)誤】操作類型不能為空', 2);
die;
}
if (!isset($param_arr['u'])) {
$this->writeLog('【傳參錯(cuò)誤】請(qǐng)求url不能為空', 2);
die;
}
if ((isset($param_arr['t']) && $param_arr['t'] < 1000)) {
$this->writeLog('【傳參錯(cuò)誤】時(shí)間不能小于1000毫秒', 2);
die;
}
if (isset($param_arr['p']) && !is_string($param_arr['p'])) {
$this->writeLog('【傳參錯(cuò)誤】請(qǐng)求參數(shù)必須是字符串', 2);
die;
}
$this->request_url = $param_arr['u'];
isset($param_arr['t']) && $this->time = $param_arr['t'];
isset($param_arr['p']) && $this->params = ['data' => $param_arr['p']];
if ($param_arr['a'] == 1) {
$this->timer_after();
}
}
/**
* 一次性定時(shí)器
* Created by 李果 En:AdoSir <1334435738@qq.com>
* @return int
*/
public function timer_after()
{
$id = swoole_timer_after($this->time, function () {
//邏輯處理更具自己情況棒拂。我的解決方案是請(qǐng)求一個(gè)地址處理訂單過期
$result = $this->cu($this->request_url, json_encode(['data' => $this->params]));
$this->writeLog("請(qǐng)求URL返回:code:{$result['httpCode']}炒俱;response:{$result['response']}", 1);
});
$this->writeLog("添加定時(shí)器【id】:{$id}【參數(shù)】:" . json_encode($this->params), 1);
return $id;
}
/**
* 發(fā)起請(qǐng)求
* @param $url
* @param array $jsonStr
* @return array
*/
public function cu($url, $jsonStr = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($jsonStr),
]
);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['httpCode' => $httpCode, 'response' => $response];
}
/**
* 寫入日志
* @param $msg
* @param int $type
*/
public function writeLog($msg, $type = 1)
{
//date_default_timezone_set("Asia/Shanghai");
$day = date('Ymd');
$file = $type == 1 ? "./log-{$day}.txt" : "./error-{$day}.txt";
file_put_contents($file, "\r\n" . date("Y - m - d h:i:s") . ': ' . $msg, FILE_APPEND | LOCK_EX);
}
}
new Timer();