This commit is contained in:
Your Name
2026-06-04 16:51:25 +08:00
parent 84eef45553
commit b566e60624
4 changed files with 127 additions and 21 deletions
+1
View File
@@ -39,6 +39,7 @@ class ExpressAutoUpdate extends Command
$result = ExpressTrackingService::autoUpdateBatch(1000); $result = ExpressTrackingService::autoUpdateBatch(1000);
$recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(1000); $recon = ExpressTrackingService::reconcileAssistantReleaseForShippedPrescriptionOrders(1000);
$duration = round(microtime(true) - $startTime, 2); $duration = round(microtime(true) - $startTime, 2);
$output->writeln("更新完成!"); $output->writeln("更新完成!");
@@ -63,29 +63,94 @@ class ExpressTrackService
$cfg = Config::get('logistics.kuaidi100', []); $cfg = Config::get('logistics.kuaidi100', []);
$enable = !empty($cfg['enable']); $enable = !empty($cfg['enable']);
$result = $out;
if (! $enable) { if (! $enable) {
$out['hint'] = '未配置快递100查询密钥或已关闭(LOGISTICS_KUAIDI100_DISABLE),仅可打开官网查件。请在 .env 中配置 LOGISTICS_KUAIDI100_CUSTOMER、LOGISTICS_KUAIDI100_KEY'; $result['hint'] = '未配置快递100查询密钥或已关闭(LOGISTICS_KUAIDI100_DISABLE),仅可打开官网查件。请在 .env 中配置 LOGISTICS_KUAIDI100_CUSTOMER、LOGISTICS_KUAIDI100_KEY';
} elseif ($phoneForKuaidi === '' && self::kuaidiPhoneRequired($kuaidiCom)) {
return $out;
}
// 顺丰(及快递100 要求电话的承运商)无 phone 时不请求接口,避免无效调用 // 顺丰(及快递100 要求电话的承运商)无 phone 时不请求接口,避免无效调用
if ($phoneForKuaidi === '' && self::kuaidiPhoneRequired($kuaidiCom)) { $result['hint'] = '顺丰查询需在快递100 中同时提交单号与收/寄件人电话(可与面单一致的完整手机号或后四位)。请填写收件电话后点「刷新轨迹」。';
$out['hint'] = '顺丰查询需在快递100 中同时提交单号与收/寄件人电话(可与面单一致的完整手机号或后四位)。请填写收件电话后点「刷新轨迹」。'; } else {
$matched = null;
return $out;
}
$lastFail = null; $lastFail = null;
foreach ($comCandidates as $tryCom) { foreach ($comCandidates as $tryCom) {
$tryOut = self::queryKuaidiOnce($cfg, $tryCom, $num, $phoneForKuaidi, $carrier, $label); $tryOut = self::queryKuaidiOnce($cfg, $tryCom, $num, $phoneForKuaidi, $carrier, $label);
if (!empty($tryOut['traces']) || ($tryOut['state'] ?? '') !== '') { if (!empty($tryOut['traces']) || ($tryOut['state'] ?? '') !== '') {
return $tryOut; $matched = $tryOut;
break;
} }
$lastFail = $tryOut; $lastFail = $tryOut;
} }
$result = $matched ?? $lastFail ?? $out;
}
return $lastFail ?? $out; // 京东自营单(JDVE…)兜底:快递100 无轨迹/陈旧时,用京东官方接口(更新或更全才采用)。
// 未配置京东官方接口时 isConfigured()=false,本段跳过,行为与原先一致。
if ($carrier === 'jd' && JdLogisticsService::isConfigured()) {
try {
$jd = JdLogisticsService::queryTrace($num);
if ($jd !== null && !empty($jd['traces']) && self::jdResultPreferred($jd, $result)) {
$result['traces'] = $jd['traces'];
$result['state'] = (string) $jd['state'];
$result['state_text'] = (string) $jd['state_text'];
$result['source'] = 'jd_official';
$result['hint'] = '';
// carrier / carrier_label / official_url / kuaidi_com 保留原值
}
} catch (\Throwable $e) {
Log::warning('ExpressTrackService jd official fallback failed', [
'num' => $num,
'error' => $e->getMessage(),
]);
}
}
return $result;
}
/**
* 京东官方轨迹是否应优先于快递100 结果采用:
* 快递100 无轨迹 → 直接用;否则京东更「新」(最新轨迹时间更晚)或同样新但条目更多 → 用。
*
* @param array{traces?:array,newest_unix?:int} $jd
* @param array{traces?:array} $kuaidi
*/
private static function jdResultPreferred(array $jd, array $kuaidi): bool
{
$kuaidiTraces = is_array($kuaidi['traces'] ?? null) ? $kuaidi['traces'] : [];
if ($kuaidiTraces === []) {
return true;
}
$jdNewest = (int) ($jd['newest_unix'] ?? 0);
$kuaidiNewest = self::newestUnixFromTraces($kuaidiTraces);
if ($jdNewest > $kuaidiNewest) {
return true;
}
if ($jdNewest === $kuaidiNewest && $jdNewest > 0) {
return count($jd['traces'] ?? []) > count($kuaidiTraces);
}
return false;
}
/**
* @param array<int, array{time?:string}> $traces
*/
private static function newestUnixFromTraces(array $traces): int
{
$best = 0;
foreach ($traces as $t) {
if (!is_array($t)) {
continue;
}
$p = strtotime((string) ($t['time'] ?? ''));
if ($p !== false && (int) $p > $best) {
$best = (int) $p;
}
}
return $best;
} }
/** /**
+40
View File
@@ -41,6 +41,32 @@ $forceDisable = filter_var($forceDisableRaw, FILTER_VALIDATE_BOOLEAN);
// 已填 CUSTOMER+KEY 即启用;需临时关闭时设 LOGISTICS_KUAIDI100_DISABLE=trueLOGISTICS_KUAIDI100_ENABLE 已废弃,可删) // 已填 CUSTOMER+KEY 即启用;需临时关闭时设 LOGISTICS_KUAIDI100_DISABLE=trueLOGISTICS_KUAIDI100_ENABLE 已废弃,可删)
$kuaidiEnabled = ! $forceDisable && $hasCreds; $kuaidiEnabled = ! $forceDisable && $hasCreds;
// 京东官方物流接口(JOS 开放平台 https://api.jd.com/routerjson
// 仅当填好 APP_KEY / APP_SECRET / ACCESS_TOKEN 时启用;作为快递100 对京东自营单(JDVE…)轨迹陈旧时的兜底数据源。
// 未配置时本段 enable=falseExpressTrackService 完全沿用现有快递100 逻辑,互不影响。
$envJd = static function (string $suffix, $default = '') use ($stripQuotes) {
$top = 'JD_LOGISTICS_' . $suffix;
$v = env($top, null);
if ($v !== null && $v !== '') {
return $stripQuotes((string) $v);
}
// 兼容误写进 [trtc] 等分区的情况
$nested = env('trtc.' . $top, $default);
return $stripQuotes((string) $nested);
};
$jdAppKey = $envJd('APP_KEY', '');
$jdAppSecret = $envJd('APP_SECRET', '');
$jdAccessToken = $envJd('ACCESS_TOKEN', '');
$jdHasCreds = $jdAppKey !== '' && $jdAppSecret !== '' && $jdAccessToken !== '';
$jdDisableRaw = env('JD_LOGISTICS_DISABLE', null);
if ($jdDisableRaw === null || $jdDisableRaw === '') {
$jdDisableRaw = env('trtc.JD_LOGISTICS_DISABLE', false);
}
$jdDisabled = filter_var($jdDisableRaw, FILTER_VALIDATE_BOOLEAN);
$jdEnabled = ! $jdDisabled && $jdHasCreds;
return [ return [
'kuaidi100' => [ 'kuaidi100' => [
'enable' => $kuaidiEnabled, 'enable' => $kuaidiEnabled,
@@ -48,4 +74,18 @@ return [
'key' => $key, 'key' => $key,
'query_url' => $envKuaidi('QUERY_URL', 'https://poll.kuaidi100.com/poll/query.do'), 'query_url' => $envKuaidi('QUERY_URL', 'https://poll.kuaidi100.com/poll/query.do'),
], ],
'jd' => [
'enable' => $jdEnabled,
'app_key' => $jdAppKey,
'app_secret' => $jdAppSecret,
'access_token' => $jdAccessToken,
'gateway' => $envJd('GATEWAY', 'https://api.jd.com/routerjson'),
// 青龙运单轨迹查询;如改用 ECLP 等可在 .env 覆盖 method / param_key / waybill_field
'method' => $envJd('METHOD', 'jingdong.ldop.receive.trace.get'),
'param_key' => $envJd('PARAM_KEY', '360buy_param_json'),
'waybill_field' => $envJd('WAYBILL_FIELD', 'waybillCode'),
// 部分接口需商家编码 / 事业部编码,可选
'customer_code' => $envJd('CUSTOMER_CODE', ''),
'api_version' => $envJd('API_VERSION', '2.0'),
],
]; ];