'mailno', 'transfer_status' => 'remark', 'received_at' => 'accept_time', ]; /** * 获取顺丰快递揽收数据 * @param array $logisticNums [logisticNums]快递单号数组 * @return array 快递揽收信息数组 * @throws Exception 未知的丰桥opCode */ public function get(array $logisticNums): array { // 将$logisticNums以10个位单位进行分割,返回二维数组 $logisticNums_size_10 = array_chunk($logisticNums, config('api_logistic.SF.max_size', 10)); // 遍历二维数组批量查询顺丰接口 //将查询到的结果整合到一起,更新order_packages.received_at $result = []; foreach ($logisticNums_size_10 as $numbers) { $numbersStr = implode(',', $numbers); $result_10 = $this->getResultFromSF(config('api_logistic.SF.head'), $numbersStr, config('api_logistic.SF.check_word'), config('api_logistic.SF.url')); $result = array_merge($result, $result_10); } return $result; } /** * @param string $head 客户号 * @param string $numbers 快递单号字符串,多个以','分隔 * @param string $checkWord 客户秘钥 * @param string $url 顺丰接口地址 * @return array * @throws Exception 未知的丰桥opCode */ public function getResultFromSF(string $head, string $numbers, string $checkWord, string $url): array { $responseBody = get_object_vars(simplexml_load_string($this->sendHttpToSF($head, $numbers, $checkWord, $url))->Body)['RouteResponse']; $result = []; if (is_array($responseBody)) {//SF返回多个单号的查询结果 $result = $this->transformSFMoreToArr($responseBody, $result); } else { $result[] = $this->transformSFOneToArr(get_object_vars($responseBody), []); } return $result; } /** * 构建顺丰xml请求体 * @param string $head * @param string $number * @return string */ private function buildXmlStr(string $head, string $number): string { return << $head xml; } /** * 将单个单号的顺丰数据转换为数组 * @param array $routeResponse * @param array $data * @return array * @throws Exception */ public function transformSFOneToArr(array $routeResponse, array $data): array { $data['logistic_number'] = $routeResponse['@attributes'][$this->protected_switch['logistic_number']]; try { $lastRoute = get_object_vars($routeResponse['Route'][count($routeResponse['Route']) - 1])['@attributes'];//获取最新的路由信息 $data = $this->switchOpCodeToStatus($lastRoute, $data); $data['transfer_status'] = $this->transformRoutes($routeResponse['Route']); if (!array_key_exists('exception', $data)) {//当顺丰返回异常时,不需要再根据时间判断是否异常,直接用顺丰的异常就好 $orderPackageReceivedSyncService = app('OrderPackageReceivedSyncService'); $exceptionData = $orderPackageReceivedSyncService->setExceptionType($data, array_key_exists('accept_time',$lastRoute) ? $lastRoute['accept_time'] : null); $data['exception_type'] = $exceptionData['exception_type']; $data['exception'] = $exceptionData['exception']; } //如果没有发现额外的异常,且查询到物流轨迹,将异常置为无 if (!array_key_exists('exception', $data) && !array_key_exists('exception_type', $data) && array_key_exists('transfer_status', $data) ) { $data['exception_type'] = '无'; $data['exception'] = '否'; } } catch (Exception $e) { throw new WarningException("单号没有查询到快递路由信息','LogisticSFService->transformSFOneToArr->{$data['logistic_number']}"); } finally { $data['routes_length'] = array_key_exists('transfer_status', $data) ? count($data['transfer_status']) : 0; return $data; } } /** * 转换快递路由信息 * @param $routs 快递路由 * @return array */ public function transformRoutes($routs): array { $result = []; if (!is_array($routs)) { $routs = [$routs]; } foreach ($routs as $route) { $route = get_object_vars($route)['@attributes']; $data['accept_time'] = $route['accept_time']; $data['accept_address'] = $route['accept_address']; $data['remark'] = $route['remark']; $result[] = $data; } return $result; } /** * 将最新路由信息转换为数组 * @param array $lastRoute * @param array $data * @return array * @throws Exception */ public function switchOpCodeToStatus(array $lastRoute, array $data): array { try { switch ($lastRoute['opcode']) { case 123: case 130: case 3036: case 31: case 30: case 36: $data['status'] = '在途'; break; case 33: $data['status'] = '派送异常'; $data['exception_type'] = '派件异常'; $data['exception'] = '是'; break; case 204: case 44: $data['status'] = '派送中'; break; case 50: $data['status'] = '已揽收'; break; case 607: case 8000: case 80: $data['status'] = '已收件'; $data['received_at'] = $lastRoute[$this->protected_switch['received_at']]; break; case 648: case 99: $data['status'] = '返回中'; break; case 70: $data['status'] = '无'; $data['exception'] = '是'; $data['exception_type'] = '其他'; break; default: throw new WarningException("未知的丰桥状态码: {$lastRoute['opcode']}->{json_encode($lastRoute)}"); } } catch (WarningException $e) { $data['status'] = '其他异常'; } finally { return $data; } } /** * @param string $head * @param string $numbers * @param string $checkWord * @param string $url * @return Response * @throws Exception */ public function sendHttpToSF(string $head, string $numbers, string $checkWord, string $url): Response { $xml = $this->buildXmlStr($head, $numbers); $checkingJson = $xml . $checkWord; $verifyCode = base64_encode(md5($checkingJson, true)); try { $response = Http::withHeaders(['Content-Type' => 'text/xml'])->get($url, ['xml' => $xml, 'verifyCode' => $verifyCode]); } catch (Exception $e) { throw new WarningException("HTTP请求顺丰接口异常->{$e->getMessage()}"); } return $response; } /** * 将多个顺丰的单号转换为数组 * @param array $responseBody * @param array $result * @return array * @throws Exception */ public function transformSFMoreToArr(array $responseBody, array $result): array { foreach ($responseBody as $routeResponse) { $result[] = $this->transformSFOneToArr(get_object_vars($routeResponse), []); } return $result; } }