1、合作商余额查询

2、分油账户司机可用额度
This commit is contained in:
2026-10-20 23:21:10 +08:00
parent 45452c1799
commit fafd3e2c16
134 changed files with 2344 additions and 352 deletions
@@ -2,8 +2,15 @@
// +----------------------------------------------------------------------
// | 企油通「运单发放结果」轮询 (7.8 waybillBatch)
// +----------------------------------------------------------------------
// | 为什么需要它:7.7 运单油费下发是【异步】接口,返回成功只代表受理,
// | 真实发放结果必须按 batch_no 调 7.8 查(《企油通接口对接文档.md》§5 第 7 条)。
// | 为什么需要它:7.7 运单油费下发是【异步】接口,返回成功只代表受理。
// |
// | 拿最终结果有两条路,判定逻辑共用 app\common\service\QytWaybill:
// | · 推:3.10 发放结果通知 -> public/qytwaybill.php -> callback:queue(主路径)
// | · 拉:本命令按 batch_no 调 7.8 查询(安全网)
// |
// | 接上推送后本命令不会下线:上游未配置接收地址不推送、推送重试 5 次后放弃、
// | 我方接收端宕机或发版重启,这三种情况都只能靠轮询兜底。此时退避间隔会自动放长,
// | 见 intervalSql()。
// |
// | 状态流转(oil_status_zsy):
// | 0 未提交 --PartnersQueue 调7.7受理--> 1 发放中 --本命令查7.8--> 2 成功 / 3 失败
@@ -27,9 +34,9 @@ namespace app\console\command;
use app\common\lock\RedisLock;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\service\QytConfig;
use app\common\service\QytOil;
use app\common\service\QytWaybill;
use think\console\Command;
use think\console\Input;
use think\console\Output;
@@ -40,15 +47,12 @@ class QytStatusQueue extends Command
/** 每轮扫描的最大订单数 */
const BATCH_LIMIT = 100;
/** 空转时的休眠秒数 */
const IDLE_SLEEP = 5;
/** 空转时的休眠秒数(只是一条轻量 SQL,不打上游,可以压到 1 秒) */
const IDLE_SLEEP = 1;
/** 查询次数达到该值后仍未定论,视为异常滞留,写告警日志 */
const STUCK_NUM = 100;
/** @var QueueService */
protected $queueService;
protected function configure()
{
$this->setName('qyt:status')
@@ -57,7 +61,7 @@ class QytStatusQueue extends Command
protected function execute(Input $input, Output $output)
{
$this->queueService = new QueueService();
// 通知下游 / 同步小程序端都在 QytWaybill 里做,这里只负责查结果
while (true) {
$handled = 0;
try {
@@ -94,11 +98,38 @@ class QytStatusQueue extends Command
/**
* 退避间隔(秒)的 SQL 表达式:查得越久间隔越长,避免高频打上游
* 前 10 次 30s,10~30 次 120s,之后 600s
*
* 为什么前几次要这么密:7.7 受理后第一次 7.8 几乎必然返回「待处理」,
* 如果第二次要等 30 秒,哪怕企油通第 8 秒就发完了,用户也得等到第 35 秒。
* 绝大多数单在前 20 秒内就有结果,所以把这一段压到 3 秒一次,
* 代价是每单多打上游几次;拖久的单自动退到长间隔,总 QPS 不会失控。
*
* 注意:若已接入 3.10 发放结果通知(后台「企油通对接设置」里打开开关),
* 走的是上面那条「安全网」曲线,不是下面这条抢时间的曲线。
*
* 次数 <6 -> 3s (覆盖 0~18s,正常单在这里出结果)
* 次数 <16 -> 10s (覆盖至约 2 分钟)
* 次数 <26 -> 30s (覆盖至约 7 分钟)
* 次数 <46 -> 120s (覆盖至约 47 分钟)
* 之后 -> 600s
*/
protected function intervalSql(): string
{
return 'CASE WHEN qyt_query_num < 10 THEN 30 WHEN qyt_query_num < 30 THEN 120 ELSE 600 END';
// 接上 3.10 发放结果通知后,结果以推送为主,轮询退为安全网:
// 只覆盖「上游没配接收地址 / 推送重试 5 次仍失败 / 我方接收端当时不可用」
// 这三种情况,没必要再抢在推送前面把上游打一遍。
if (!empty(QytConfig::get()['waybill_notify'])) {
return 'CASE'
. ' WHEN qyt_query_num < 3 THEN 60'
. ' WHEN qyt_query_num < 8 THEN 300'
. ' ELSE 900 END';
}
return 'CASE'
. ' WHEN qyt_query_num < 6 THEN 3'
. ' WHEN qyt_query_num < 16 THEN 10'
. ' WHEN qyt_query_num < 26 THEN 30'
. ' WHEN qyt_query_num < 46 THEN 120'
. ' ELSE 600 END';
}
/**
@@ -143,31 +174,16 @@ class QytStatusQueue extends Command
return;
}
// 判定与状态跃迁跟 3.10 推送共用 QytWaybill,两条路结论必须一致
$batchStatus = (int) ($res['data']['status'] ?? 0);
$row = QytOil::pickWaybill($res['data'], (string) $order['waybill_no_code']);
$row = QytWaybill::pick($res['data'], (string) $order['waybill_no_code']);
$judged = QytWaybill::judge($batchStatus, $row);
// 整批处理失败,且明细里没有本条 -> 判失败
if (!$row && $batchStatus === 5) {
$this->markFailed($order, '企油通返回整批处理失败', $qyt);
return;
}
// 明细里还没有本条:批次还在排队,继续等
if (!$row) {
if ($judged['result'] === QytWaybill::R_PENDING) {
$this->alertIfStuck($order, $qyt);
return;
}
switch ($row['handle_status']) {
case 2: // 成功
$this->markSuccess($order, $row['remark'] ?: '发放成功', $qyt);
break;
case 3: // 失败
$this->markFailed($order, $row['error_msg'] ?: '企油通发放失败', $qyt);
break;
default: // 1 待处理,或未知值 -> 继续等
$this->alertIfStuck($order, $qyt);
break;
}
QytWaybill::apply($order, $judged, '7.8 轮询');
} catch (\Throwable $e) {
$this->log("order={$orderId} 轮询异常:" . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
} finally {
@@ -175,60 +191,6 @@ class QytStatusQueue extends Command
}
}
/**
* 发放成功:条件跃迁 1 -> 2,胜出者才回调下单平台
*/
protected function markSuccess(array $order, string $text, QytOil $qyt): void
{
$affected = Db::name('partners_order')
->where('order_id', $order['order_id'])
->where('oil_status_zsy', 1)
->update([
'oil_status' => 1,
'oil_status_zsy' => 2,
'oil_time' => time(),
'oil_text' => $text,
'update_time' => time(),
]);
if (!$affected) {
return;
}
$this->queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
$qyt->saveLog("7.8 确认发放成功 order={$order['order_id']} batch_no={$order['batch_no']}"
. " 查询次数={$order['qyt_query_num']}");
}
/**
* 发放失败:条件跃迁 1 -> 3,胜出者才退款(保证只退一次)
*/
protected function markFailed(array $order, string $reason, QytOil $qyt): void
{
$affected = Db::name('partners_order')
->where('order_id', $order['order_id'])
->where('oil_status_zsy', 1)
->update([
'oil_status_zsy' => 3,
'oil_text' => '发放失败:' . $reason,
'update_time' => time(),
]);
if (!$affected) {
return;
}
PartnersBalance::refund(
(int) $order['partners_id'],
(float) $order['total_price'],
(string) $order['partners_order_no'],
(int) $order['order_id'],
(int) $order['store_id']
);
$qyt->saveLog("7.8 确认发放失败 order={$order['order_id']} 原因={$reason},已回补合作商余额 {$order['total_price']}");
}
/**
* 长期拿不到结果时告警。只写日志,不改状态、不退款
*/