Files
oil/server-tp6/app/console/command/PartnersQueue.php
T

213 lines
9.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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\lock\RedisLock;
use app\common\model\partners\Order as OrderModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\service\QytOil;
use think\console\Command;
use think\console\Input;
use think\console\Output;
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('下单平台订单 -> 企油通运单油费下发(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);
}
}
}
protected function processMessage($message)
{
$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;
}
// 每个订单串行处理;锁 60s 自动过期,崩溃不会死锁
$lock = new RedisLock('qyt:send:' . $orderId, 60);
if (!$lock->acquireLock()) {
$qyt->saveLog("partners_queue 订单处理中,跳过 order={$orderId}");
return;
}
try {
$order = OrderModel::detail($orderId);
if (!$order) {
$qyt->saveLog('partners_queue 未找到订单:' . $orderId);
return;
}
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();
}
}
/**
* 发放失败 -> 回补合作商余额(仅在状态跃迁胜出时调用,天然幂等)
*/
protected function refundBalance($order)
{
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']}");
}
/**
* 兜底巡检(限频 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());
}
}
}