第一次更新,完成了初版调整
This commit is contained in:
@@ -1,36 +1,54 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 下单平台订单 -> 企油通「运单油费下发」(7.7)
|
||||
// +----------------------------------------------------------------------
|
||||
// | OilCard::share 创建 yoshop_partners_order 后 push 'partners_queue',本命令消费:
|
||||
// | 1. 每个 order_id 用 RedisLock 串行化,锁内重读订单状态
|
||||
// | 2. 生成企油通全平台唯一编号(确定性派生,可安全重试)
|
||||
// | 3. 调 7.7 createAndSendQuota;支付宝企业码通道下「受理成功即完成发放」
|
||||
// | 4. 发放成功 -> oil_status=1 / oil_status_zsy=2,投 notify_queue 回调下单平台
|
||||
// |
|
||||
// | 并发/原子性:
|
||||
// | · oil_status_zsy 只在 7.7 结果确定后离开 0;空闲时 reconcile() 补投丢失的队列消息
|
||||
// | · 状态跃迁为条件 UPDATE(... WHERE oil_status_zsy=0),affected=1 才回调,天然幂等
|
||||
// | · batch_no 确定性派生 -> 崩溃重试用同一值,依赖企油通按 batch_no 幂等去重
|
||||
// | 启动:php think partners:queue(可多进程;RedisLock 保证同一订单串行)
|
||||
// +----------------------------------------------------------------------
|
||||
namespace app\console\command;
|
||||
|
||||
use app\common\service\IdingjuOil;
|
||||
use app\common\lock\RedisLock;
|
||||
use app\common\model\partners\Order as OrderModel;
|
||||
use app\common\service\PartnersBalance;
|
||||
use app\common\service\QueueService;
|
||||
use app\store\model\Company as CompanyModel;
|
||||
use app\common\service\QytOil;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use app\common\model\partners\Order as OrderModel;
|
||||
use think\facade\Db;
|
||||
|
||||
class PartnersQueue extends Command
|
||||
{
|
||||
/** @var QueueService */
|
||||
protected $queueService;
|
||||
|
||||
/** @var int 上次兜底巡检时间 */
|
||||
protected $lastReconcile = 0;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('partners:queue')
|
||||
->setDescription('Process messages from the Redis queue');
|
||||
->setDescription('下单平台订单 -> 企油通运单油费下发(7.7)');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$this->queueService = new QueueService();
|
||||
|
||||
while (true) {
|
||||
$message = $this->queueService->pop('partners_queue');
|
||||
if ($message) {
|
||||
// 处理消息
|
||||
$this->processMessage($message);
|
||||
} else {
|
||||
// 暂停一段时间再检查队列
|
||||
$this->reconcile();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
@@ -38,103 +56,157 @@ class PartnersQueue extends Command
|
||||
|
||||
protected function processMessage($message)
|
||||
{
|
||||
$IdingjuOil = new IdingjuOil();
|
||||
$IdingjuOil->saveLogStore('partners_queue队列数据处理:【'.$message.'】');
|
||||
// 拿到队列数据处理
|
||||
$data = json_decode($message, true);
|
||||
// 检查解码是否成功
|
||||
if ($data === false) {
|
||||
$IdingjuOil->saveLogStore('partners_queue队列数据解码失败:' . json_last_error_msg());
|
||||
$qyt = new QytOil();
|
||||
$data = is_array($message) ? $message : json_decode($message, true);
|
||||
$orderId = $data['order_id'] ?? null;
|
||||
if (!$orderId) {
|
||||
$qyt->saveLog('partners_queue 消息缺少 order_id:' . json_encode($message, 320));
|
||||
return;
|
||||
}
|
||||
$order = OrderModel::detail($data['order_id']);
|
||||
if (!$order) {
|
||||
$IdingjuOil->saveLogStore('未找到对应订单:【' . $data['order_id'] . '】');
|
||||
|
||||
// 每个订单串行处理;锁 60s 自动过期,崩溃不会死锁
|
||||
$lock = new RedisLock('qyt:send:' . $orderId, 60);
|
||||
if (!$lock->acquireLock()) {
|
||||
$qyt->saveLog("partners_queue 订单处理中,跳过 order={$orderId}");
|
||||
return;
|
||||
}
|
||||
// 此处可以根据业务逻辑处理接收到的数据
|
||||
|
||||
try {
|
||||
if($order['order_status'] == 10 && $order['oil_status'] == 0 && $order['oilCardType'] == 'zsy_ticket'){
|
||||
$data['amount'] = $order['total_price']; // 金额
|
||||
$company = CompanyModel::detail(['orgNo' => $this->getCompany($order['goods_no'])]);
|
||||
if (!$company) {
|
||||
$IdingjuOil->saveLogStore('没有可用参数:【' . $order['goods_no'] . '】');
|
||||
return;
|
||||
}
|
||||
|
||||
// 对接平台接口
|
||||
$res = $IdingjuOil->oilOilCardShare([
|
||||
'goodsNo'=>$order['goods_no'],
|
||||
'amount'=>$data['amount'],
|
||||
'userNo'=>'',
|
||||
'mobile'=>$order['mobile'],
|
||||
'saleOrderNo'=>$order['oilCardNo'],
|
||||
'oilCardType'=>$order['oilCardType'],
|
||||
'notify_url'=>config('app.base_url') . 'partnersoil.php',
|
||||
'use_notify_url'=>config('app.base_url') . 'partnersoiluse.php'
|
||||
], $company);
|
||||
|
||||
if ($res['code'] != 1) {
|
||||
$IdingjuOil->saveLogStore(($res['errMessage'] ?? $res['message']));
|
||||
return;
|
||||
}
|
||||
$item['shareNo'] = $res['data']['trade_no'] ?? '';
|
||||
$item['qrCode'] = $res['data']['useUrl'] ?? '';
|
||||
$item['oil_status_zsy'] = $res['data']['state'] ?? 0;
|
||||
$item['oil_time'] = time();
|
||||
$item['oil_status'] = $item['oil_status_zsy'] == 2 ? 1 : 0; // 已发券 为1
|
||||
$resUpdate = $order->save($item);
|
||||
if (!$resUpdate) {
|
||||
$IdingjuOil->saveLogStore("订单更新失败,order_id: {$data['order_id']}");
|
||||
}
|
||||
if($item['oil_status_zsy'] == 2) {
|
||||
// 存入队列处理
|
||||
$queueService = new QueueService();
|
||||
$queueService->push('notify_queue', json_encode([
|
||||
'order_id' => $order['order_id'],
|
||||
'type' => 'add',
|
||||
'optType' => 'share_oil',
|
||||
'retry_count' => 0,
|
||||
], 320));
|
||||
}
|
||||
$order = OrderModel::detail($orderId);
|
||||
if (!$order) {
|
||||
$qyt->saveLog('partners_queue 未找到订单:' . $orderId);
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$IdingjuOil->saveLogStore(
|
||||
'partners_queue队列处理异常:' . $e->getMessage() .
|
||||
' 在文件:' . $e->getFile() .
|
||||
' 第:' . $e->getLine() .
|
||||
' 行。堆栈跟踪:' . $e->getTraceAsString()
|
||||
);
|
||||
if ($order['order_status'] != 10 || $order['oil_status'] != 0 || (int) $order['oil_status_zsy'] !== 0) {
|
||||
$qyt->saveLog("partners_queue 订单状态不满足下发条件 order={$orderId}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 确定性派生编号(含企业前缀),重试时保持一致
|
||||
$seed = $order['partners_order_no'] ?: (string) $order['order_id'];
|
||||
$batchNo = QytOil::genNo('B', $seed);
|
||||
$waybillNo = QytOil::genNo('W', $seed);
|
||||
$waybillNoCode = QytOil::genNo('C', $seed);
|
||||
$name = trim((string) $order['driver_name']);
|
||||
if ($name === '' || $order['mobile'] === '') {
|
||||
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
|
||||
->update(['oil_status_zsy' => 3, 'oil_text' => '缺司机姓名/手机号,无法发放', 'update_time' => time()]);
|
||||
$failed && $this->refundBalance($order);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 先落编号(状态不动,仍为 0)
|
||||
Db::name('partners_order')->where('order_id', $orderId)->update([
|
||||
'batch_no' => $batchNo,
|
||||
'waybill_no' => $waybillNo,
|
||||
'waybill_no_code' => $waybillNoCode,
|
||||
'oil_text' => '发放中',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
|
||||
// 3. 7.7 运单油费下发
|
||||
$res = $qyt->createAndSendQuota([
|
||||
'batch_no' => $batchNo,
|
||||
'waybill_no' => $waybillNo,
|
||||
'waybill_no_code' => $waybillNoCode,
|
||||
'mobile' => $order['mobile'],
|
||||
'name' => $name,
|
||||
'amount' => $order['total_price'],
|
||||
]);
|
||||
|
||||
// 失败(且非 batch 已存在)
|
||||
if ($res['code'] !== '00' && empty($res['dup_batch'])) {
|
||||
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
|
||||
'oil_status_zsy' => 3,
|
||||
'oil_text' => '下发失败:' . $res['message'],
|
||||
'update_time' => time(),
|
||||
]);
|
||||
$failed && $this->refundBalance($order);
|
||||
$qyt->saveLog("7.7 下发失败 order={$orderId}:" . $res['message']);
|
||||
return;
|
||||
}
|
||||
// data.status: 1 成功 / 2 失败(dup_batch 视为已受理成功)
|
||||
$status = empty($res['dup_batch']) ? (string) ($res['data']['status'] ?? '1') : '1';
|
||||
if ($status === '2') {
|
||||
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
|
||||
'oil_status_zsy' => 3,
|
||||
'oil_text' => $res['data']['msg'] ?? '下发失败',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
$failed && $this->refundBalance($order);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 受理成功 = 发放成功(支付宝企业码通道);条件跃迁 0 -> 2,胜出者回调下单平台
|
||||
$affected = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
|
||||
'oil_status' => 1,
|
||||
'oil_status_zsy' => 2,
|
||||
'oil_time' => time(),
|
||||
'shareNo' => $waybillNoCode,
|
||||
'oil_text' => $res['data']['msg'] ?? '发放成功',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
if ($affected) {
|
||||
$this->queueService->push('notify_queue', json_encode([
|
||||
'order_id' => $orderId,
|
||||
'type' => 'add',
|
||||
'optType' => 'share_oil',
|
||||
'retry_count' => 0,
|
||||
], 320));
|
||||
$qyt->saveLog("7.7 发放成功 order={$orderId} batch_no={$batchNo}"
|
||||
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已发放处理)'));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 不改状态(仍为 0),交由 reconcile 兜底重投
|
||||
$qyt->saveLog('partners_queue 异常 order=' . $orderId . ':' . $e->getMessage()
|
||||
. ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
} finally {
|
||||
$lock->releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $goodsNo
|
||||
* @return string
|
||||
* 发放失败 -> 回补合作商余额(仅在状态跃迁胜出时调用,天然幂等)
|
||||
*/
|
||||
public function getCompany($goodsNo): string
|
||||
protected function refundBalance($order)
|
||||
{
|
||||
switch ($goodsNo) {
|
||||
case 'djgdface100':
|
||||
case 'djgdface200':
|
||||
case 'djgdface300':
|
||||
return 'umQZhIHKofyA';
|
||||
case 'jmgxyyface100':
|
||||
case 'jmgxyyface200':
|
||||
case 'jmgxyyface300':
|
||||
PartnersBalance::refund(
|
||||
(int) $order['partners_id'],
|
||||
(float) $order['total_price'],
|
||||
(string) $order['partners_order_no'],
|
||||
(int) $order['order_id'],
|
||||
(int) $order['store_id']
|
||||
);
|
||||
(new QytOil())->saveLog("发放失败已回补合作商余额 order={$order['order_id']} amount={$order['total_price']}");
|
||||
}
|
||||
|
||||
case 'jmgzface100':
|
||||
case 'jmgzface200':
|
||||
case 'jmgzface300':
|
||||
|
||||
case 'jmfjface100':
|
||||
case 'jmfjface200':
|
||||
case 'jmfjface300':
|
||||
return 'HpWqXIgYvTBg';
|
||||
case 'face003':
|
||||
return 'NKJBgaaRNdiL'; // 测试参数
|
||||
default:
|
||||
return '';
|
||||
/**
|
||||
* 兜底巡检(限频 60s):补投丢失的 partners_queue 消息。
|
||||
* 因下发为条件跃迁,重复投递安全。
|
||||
*/
|
||||
protected function reconcile()
|
||||
{
|
||||
if (time() - $this->lastReconcile < 60) {
|
||||
return;
|
||||
}
|
||||
$this->lastReconcile = time();
|
||||
$now = time();
|
||||
try {
|
||||
$ids = Db::name('partners_order')
|
||||
->where('order_status', 10)
|
||||
->where('oil_status_zsy', 0)
|
||||
->where('oilCardType', 'zsy_ticket')
|
||||
->where('create_time', '<', $now - 120)
|
||||
->where('create_time', '>', $now - 86400)
|
||||
->limit(200)->column('order_id');
|
||||
foreach ($ids as $oid) {
|
||||
$this->queueService->push('partners_queue', json_encode(['order_id' => $oid], 320));
|
||||
}
|
||||
if ($ids) {
|
||||
(new QytOil())->saveLog('partners_queue reconcile 补投 ' . count($ids) . ' 单');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
(new QytOil())->saveLog('partners_queue reconcile 异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user