This commit is contained in:
Your Name
2026-05-20 15:40:01 +08:00
parent abf8026d45
commit 3e4039efb0
21 changed files with 2507 additions and 532 deletions
+148 -16
View File
@@ -45,6 +45,7 @@ class ExpressTrackService
$carrier = $resolved['carrier'];
$kuaidiCom = $resolved['kuaidi_com'];
$label = $resolved['label'];
$comCandidates = self::kuaidiComCandidates($kuaidiCom, $num);
$officialUrl = self::buildOfficialUrl($carrier, $num);
@@ -75,6 +76,74 @@ class ExpressTrackService
return $out;
}
$lastFail = null;
foreach ($comCandidates as $tryCom) {
$tryOut = self::queryKuaidiOnce($cfg, $tryCom, $num, $phoneForKuaidi, $carrier, $label);
if (!empty($tryOut['traces']) || ($tryOut['state'] ?? '') !== '') {
return $tryOut;
}
$lastFail = $tryOut;
}
return $lastFail ?? $out;
}
/**
* 根据运单号形态纠正承运商(避免 express_tracking 误存 sf 导致京东单查不出)
*/
public static function normalizeExpressCompanyCode(string $trackingNumber, string $storedCompany = 'auto'): string
{
$byNumber = self::detectCarrierFromNumber($trackingNumber);
if ($byNumber === null) {
$ec = strtolower(trim($storedCompany));
return in_array($ec, ['sf', 'jd', 'jt', 'jtexpress', 'auto'], true) ? $ec : 'auto';
}
$ec = strtolower(trim($storedCompany));
$byEc = self::carrierFromExpressCode($ec);
if ($byEc !== null && $byEc['carrier'] !== $byNumber['carrier']) {
return $byNumber['carrier'];
}
return $byNumber['carrier'];
}
/**
* @param array<string, mixed> $cfg
* @return array{
* carrier: string,
* carrier_label: string,
* kuaidi_com: string,
* traces: list<array{time:string,context:string}>,
* state: string,
* state_text: string,
* source: string,
* hint: string,
* official_url: string
* }
*/
private static function queryKuaidiOnce(
array $cfg,
string $kuaidiCom,
string $num,
string $phoneForKuaidi,
string $carrier,
string $label
): array {
$officialUrl = self::buildOfficialUrl($carrier, $num);
$out = [
'carrier' => $carrier,
'carrier_label' => $label,
'kuaidi_com' => $kuaidiCom,
'traces' => [],
'state' => '',
'state_text' => '',
'source' => 'kuaidi100',
'hint' => '',
'official_url' => $officialUrl,
];
$paramArr = [
'com' => $kuaidiCom,
'num' => $num,
@@ -98,7 +167,7 @@ class ExpressTrackService
$raw = self::httpPostForm($url, $postBody);
if ($raw === null || $raw === '') {
$out['hint'] = '快递100接口无响应,请稍后重试或使用官网查询';
Log::warning('ExpressTrackService kuaidi100 empty response', ['num' => $num]);
Log::warning('ExpressTrackService kuaidi100 empty response', ['num' => $num, 'com' => $kuaidiCom]);
return $out;
}
@@ -106,7 +175,7 @@ class ExpressTrackService
$json = json_decode($raw, true);
if (!is_array($json)) {
$out['hint'] = '快递100返回异常,请使用官网查询';
Log::warning('ExpressTrackService kuaidi100 invalid json', ['raw' => mb_substr($raw, 0, 500)]);
Log::warning('ExpressTrackService kuaidi100 invalid json', ['raw' => mb_substr($raw, 0, 500), 'com' => $kuaidiCom]);
return $out;
}
@@ -114,19 +183,16 @@ class ExpressTrackService
if (isset($json['result']) && $json['result'] === false) {
$msg = (string) ($json['message'] ?? '查询失败');
$returnCode = (string) ($json['returnCode'] ?? '');
// 特殊处理"找不到对应公司"错误
if ($msg === '找不到对应公司' || $returnCode === '400') {
$out['hint'] = '快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询';
} else {
$out['hint'] = $msg;
}
Log::info('ExpressTrackService kuaidi100 business fail', [
'message' => $msg,
'returnCode' => $returnCode,
'num' => $num,
'com' => $kuaidiCom
'com' => $kuaidiCom,
]);
return $out;
@@ -138,7 +204,7 @@ class ExpressTrackService
}
if (($json['message'] ?? '') !== 'ok' && $data === []) {
$out['hint'] = (string) ($json['message'] ?? '未查到轨迹');
Log::info('ExpressTrackService kuaidi100 no data', ['json' => $json]);
Log::info('ExpressTrackService kuaidi100 no data', ['json' => $json, 'com' => $kuaidiCom]);
return $out;
}
@@ -159,12 +225,35 @@ class ExpressTrackService
$out['traces'] = $traces;
$out['state'] = (string) ($json['state'] ?? '');
$out['state_text'] = self::stateText($out['state']);
$out['source'] = 'kuaidi100';
$out['hint'] = $traces === [] ? '暂无轨迹节点,单号可能尚未揽收' : '';
return $out;
}
/**
* @return list<string>
*/
private static function kuaidiComCandidates(string $primaryCom, string $num): array
{
$list = [$primaryCom];
$byNumber = self::detectCarrierFromNumber($num);
if ($byNumber !== null && !in_array($byNumber['kuaidi_com'], $list, true)) {
$list[] = $byNumber['kuaidi_com'];
}
if (preg_match('/^JDVE/i', strtoupper($num)) && !in_array('jd', $list, true)) {
$list[] = 'jd';
}
if (preg_match('/^(JD|JDV|JDK|JDEX)/i', strtoupper($num))) {
foreach (['jingdong', 'jd'] as $c) {
if (!in_array($c, $list, true)) {
$list[] = $c;
}
}
}
return array_values(array_unique(array_filter($list, static fn ($c) => $c !== '' && $c !== 'auto')));
}
/**
* 快递100「phone」入参:有手动覆盖且不少于 4 位时用覆盖;否则用订单收货号码。
* 对 11 位及以上数字取后 11 位作为手机号(去掉可能的前缀符号位)。
@@ -191,32 +280,75 @@ class ExpressTrackService
}
/**
* @return array{carrier: string, kuaidi_com: string, label: string}
* @return array{carrier: string, kuaidi_com: string, label: string}|null
*/
private static function resolveCarrier(string $expressCompany, string $num): array
private static function carrierFromExpressCode(string $expressCompany): ?array
{
$ec = strtolower(trim($expressCompany));
if ($ec === 'sf') {
if ($ec === 'sf' || $ec === 'shunfeng') {
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运'];
}
if ($ec === 'jd') {
if ($ec === 'jd' || $ec === 'jingdong') {
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递'];
}
if ($ec === 'jt' || $ec === 'jtexpress') {
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递'];
}
$u = strtoupper($num);
if (preg_match('/^SF\d/i', $num)) {
return null;
}
/**
* @return array{carrier: string, kuaidi_com: string, label: string}|null
*/
private static function detectCarrierFromNumber(string $num): ?array
{
$n = trim($num);
if ($n === '') {
return null;
}
$u = strtoupper($n);
if (preg_match('/^SF\d/i', $n)) {
return ['carrier' => 'sf', 'kuaidi_com' => self::KUAIDI_COM_SF, 'label' => '顺丰速运(单号识别)'];
}
if (preg_match('/^(JD|JDV|JDK|JDEX|JD[A-Z0-9])/i', $u)) {
if (preg_match('/^JDVE/i', $u)) {
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东快递(单号识别)'];
}
if (preg_match('/^JT\d{13}$/i', $num)) {
if (preg_match('/^(JDK|JDV|JDEX)/i', $u) || preg_match('/^JD[A-Z0-9]{10,}/i', $u)) {
return ['carrier' => 'jd', 'kuaidi_com' => self::KUAIDI_COM_JD, 'label' => '京东物流(单号识别)'];
}
if (preg_match('/^JT\d{13}$/i', $n)) {
return ['carrier' => 'jt', 'kuaidi_com' => self::KUAIDI_COM_JT, 'label' => '极兔速递(单号识别)'];
}
return null;
}
/**
* @return array{carrier: string, kuaidi_com: string, label: string}
*/
private static function resolveCarrier(string $expressCompany, string $num): array
{
$byNumber = self::detectCarrierFromNumber($num);
$byEc = self::carrierFromExpressCode($expressCompany);
if ($byNumber !== null && $byEc !== null && $byEc['carrier'] !== $byNumber['carrier']) {
Log::info('ExpressTrackService carrier mismatch, prefer tracking number', [
'express_company' => $expressCompany,
'tracking_number' => $num,
'stored_carrier' => $byEc['carrier'],
'detected_carrier' => $byNumber['carrier'],
]);
return $byNumber;
}
if ($byEc !== null) {
return $byEc;
}
if ($byNumber !== null) {
return $byNumber;
}
return ['carrier' => 'auto', 'kuaidi_com' => 'auto', 'label' => '自动识别'];
}