文件
Henglianshutong/addons/shopro/service/partner/PartnerCompensateService.php

198 行
7.2 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace addons\shopro\service\partner;
use think\Db;
use app\admin\model\shopro\order\Order as OrderModel;
use app\admin\model\shopro\partner\Reward as RewardModel;
use app\admin\model\shopro\commission\CommissionGoods;
/**
* 恒联数通 · 订单收益自动对账与补偿服务 (Production-Grade Auto-Compensation)
*
* 职责:
* 1. 扫描线上已支付但由于队列崩溃、丢单、重试上限或Redis故障未完成收益结算的合格订单;
* 2. 具备绝对幂等性安全补偿发放,杜绝漏发与重复发放;
* 3. 提供收益中心健康度诊断与对账审计报表。
*/
class PartnerCompensateService
{
/**
* 查询未结算收益的合格订单
*
* @param int $limit 查询数量上限
* @param int|null $startTime 起始支付时间戳
* @param int|null $endTime 结束支付时间戳
* @return \think\Collection
*/
public function findUnsettledOrders($limit = 100, $startTime = null, $endTime = null)
{
$rateService = new RateService();
$query = $rateService->qualifiedOrderQuery($startTime, $endTime);
$orderTable = (new OrderModel())->getQuery()->getTable();
$rewardTable = (new RewardModel())->getQuery()->getTable();
// 核心排除:在 shopro_partner_reward 表中已存在该订单的有效流水或标记
$query->whereNotExists(function ($q) use ($rewardTable) {
$q->table($rewardTable)->alias('pr')
->whereRaw('pr.order_id = o.id')
->where('pr.order_id', '>', 0);
});
return $query->order('o.id', 'asc')->limit($limit)->select();
}
/**
* 查询结算标记失败的异常订单
*
* @param int $limit
* @return \think\Collection
*/
public function findFailedMarkedOrders($limit = 50)
{
return OrderModel::alias('o')
->where('o.type', '<>', 'score')
->where('o.pay_fee', '>', 0)
->whereIn('o.status', [OrderModel::STATUS_PAID, OrderModel::STATUS_COMPLETED])
->where('o.ext', 'like', '%"reward_settle_failed":1%')
->order('o.id', 'asc')
->limit($limit)
->select();
}
/**
* 执行自动对账与收益安全补偿
*
* @param int $limit 单次最大补偿订单数
* @param bool $dryRun 是否仅试算对账不写入
* @return array 对账与补偿执行报表
*/
public function compensateAll($limit = 100, $dryRun = false)
{
$unsettled = $this->findUnsettledOrders($limit);
$rewardService = new PartnerRewardService();
$report = [
'total_found' => count($unsettled),
'dry_run' => $dryRun,
'success_count' => 0,
'failed_count' => 0,
'skipped_count' => 0,
'details' => [],
'time' => date('Y-m-d H:i:s'),
];
foreach ($unsettled as $order) {
$orderId = intval($order->id);
$orderSn = $order->order_sn;
$payFee = (float) $order->pay_fee;
if ($dryRun) {
$report['details'][] = [
'order_id' => $orderId,
'order_sn' => $orderSn,
'pay_fee' => $payFee,
'status' => 'preview',
'message' => '待补偿',
];
continue;
}
try {
$result = Db::transaction(function () use ($rewardService, $orderId) {
return $rewardService->settleOrder($orderId);
});
if ($result) {
$report['success_count']++;
$rewards = RewardModel::where('order_id', $orderId)->where('order_id', '>', 0)->select();
$cashTotal = 0;
$types = [];
foreach ($rewards as $r) {
if ((float) $r->amount > 0 && $r->status == 1) {
$cashTotal = bcadd((string)$cashTotal, (string)$r->amount, 2);
}
$types[] = $r->reward_type;
}
$report['details'][] = [
'order_id' => $orderId,
'order_sn' => $orderSn,
'pay_fee' => $payFee,
'status' => 'success',
'cash_distributed' => $cashTotal,
'reward_types' => array_values(array_unique($types)),
];
} else {
$report['skipped_count']++;
$report['details'][] = [
'order_id' => $orderId,
'order_sn' => $orderSn,
'pay_fee' => $payFee,
'status' => 'skipped',
'message' => '订单不满足计奖条件或已结',
];
}
} catch (\Throwable $e) {
$report['failed_count']++;
$errorMsg = $e->getMessage();
\think\Log::record("[PartnerCompensate] 订单 ID:{$orderId} 补偿执行异常: {$errorMsg}", 'error');
$report['details'][] = [
'order_id' => $orderId,
'order_sn' => $orderSn,
'pay_fee' => $payFee,
'status' => 'failed',
'error' => $errorMsg,
];
}
}
return $report;
}
/**
* 收益中心与队列运行健康度全景诊断
*
* @return array
*/
public function checkHealth()
{
$queueConfig = \think\Config::get('queue');
$expire = $queueConfig['expire'] ?? null;
$selectDb = $queueConfig['select'] ?? null;
$unsettledCount = count($this->findUnsettledOrders(500));
$failedMarkCount = count($this->findFailedMarkedOrders(500));
$warnings = [];
if ($expire === 0) {
$warnings[] = "致命配置警告:queue.expire 为 0,会导致队列任务被反复超时重放与并发重复消费!";
} elseif ($expire === null || $expire < 60) {
$warnings[] = "配置建议:queue.expire 应设置为 >= 60 秒(推荐 180 秒)。";
}
if ($unsettledCount > 0) {
$warnings[] = "发现 {$unsettledCount} 笔已支付但未进行收益结算的订单,建议执行补偿操作。";
}
if ($failedMarkCount > 0) {
$warnings[] = "发现 {$failedMarkCount} 笔标记为结算重试失败的订单,需重点关注。";
}
return [
'status' => empty($warnings) ? 'healthy' : ($expire === 0 ? 'critical' : 'warning'),
'queue' => [
'connector' => $queueConfig['connector'] ?? 'unknown',
'expire_seconds' => $expire,
'redis_db' => $selectDb,
'default_queue' => $queueConfig['default'] ?? 'default',
],
'unsettled_orders_count' => $unsettledCount,
'failed_marked_orders_count' => $failedMarkCount,
'warnings' => $warnings,
'check_time' => date('Y-m-d H:i:s'),
];
}
}