| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- <?php
- namespace App\Services;
- use App\Exceptions\WarningException;
- use App\OrderPackage;
- use Exception;
- use Illuminate\Http\Client\Response;
- use Illuminate\Support\Carbon;
- use Illuminate\Support\Facades\Http;
- class LogisticSFService
- {
- /**
- * 顺丰字段与数据库字段的映射关心
- * @var string[]
- */
- protected $protected_switch = [
- 'logistic_number' => '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 <<<xml
- <?xml version="1.0" encoding="utf-8" ?>
- <Request service='RouteService' lang='zh-CN'>
- <Head>$head</Head>
- <Body>
- <RouteRequest
- tracking_number="{$number}" tracking_type='1' method_type='1'
- />
- </Body>
- </Request>
- 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)) {//当顺丰返回异常时,不需要再根据时间判断是否异常,直接用顺丰的异常就好
- $data = $this->setExceptionType($data, $lastRoute['accept_time']);
- }
- } catch (Exception $e) {
- throw new WarningException("单号没有查询到快递路由信息','LogisticSFService->transformSFOneToArr->{$data['logistic_number']}");
- } finally {
- return $data;
- }
- }
- /**
- * 转换快递路由信息
- * @param array $routs 快递路由
- * @return array
- */
- public function transformRoutes(array $routs): array
- {
- $result = [];
- 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 70:
- 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;
- 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;
- }
- /**
- * @param array $data
- * @param $lastRouteDate
- * @return array
- */
- private function setExceptionType(array $data, $lastRouteDate = null): array
- {
- $logistic_number = $data['logistic_number'];
- /** @var OrderPackage $orderPackage */
- $orderPackage = OrderPackage::query()->with('order')->where('logistic_number', $logistic_number)->first();
- $delivered_duration = now()->diffInHours(Carbon::parse($orderPackage['sent_at']));
- $last_routed_duration = now()->diffInHours(Carbon::parse($lastRouteDate));
- $VALID_HOURS = 4;
- $SHORT_RESPONSE_HOURS = 24;
- $LONG_RESPONSE_HOURS = (function ($province) {
- switch ($province) {
- case '浙江省':
- case '江苏省':
- case '上海':
- case '安徽省':
- return 72;
- case '北京':
- case '天津':
- case '江西省':
- case '湖北省':
- case '湖南省':
- case '广东省':
- case '福建省':
- case '山东省':
- case '河北省':
- case '河南省':
- case '山西省':
- case '四川省':
- case '陕西省':
- case '重庆':
- case '广西壮族自治区':
- case '贵州省':
- case '云南省':
- case '海南省':
- case '吉林省':
- case '黑龙江省':
- case '辽宁省':
- return 120;
- case '青海省':
- case '宁夏回族自治区':
- case '甘肃省':
- case '内蒙古自治区':
- case '新疆维吾尔自治区':
- case '西藏自治区':
- return 168;
- default:
- break;
- }
- })($orderPackage->order->province);
- $SENDING_RESPONSE_HOURS = 48;
- $IS_ROUTED = 1; //0000 0001 有路由信息
- $IS_IN_VALID_TIME = 2; //0000 0010 大于4小时
- $IS_WEIGHED = 4; //0000 0100 称重过
- $IS_RECEIVED = 8; //0000 1000 已经收货
- $IS_SENDING = 16; //0001 0000 正在派送
- $IS_SHORT_NO_RESPONSE = 32; //0010 0000 中转异常
- $IS_LONG_NO_RESPONSE = 64; //0010 0000 疑似丢件
- $IS_SENDING_NO_RESPONSE = 128; //0010 0000 派送异常
- $conclusion = (function () use (
- $data, $delivered_duration, $last_routed_duration,
- $VALID_HOURS, $IS_ROUTED, $IS_IN_VALID_TIME, $IS_WEIGHED, $IS_RECEIVED, $IS_SENDING, $IS_SHORT_NO_RESPONSE, $IS_LONG_NO_RESPONSE, $IS_SENDING_NO_RESPONSE,
- $SHORT_RESPONSE_HOURS, $LONG_RESPONSE_HOURS, $SENDING_RESPONSE_HOURS,
- $orderPackage
- ) {
- $conclusion = 0;
- $conclusion |= !empty($data['transfer_status']) ? $IS_ROUTED : 0;
- $conclusion |= ($delivered_duration > $VALID_HOURS) ? $IS_IN_VALID_TIME : 0;
- $conclusion |= ($orderPackage->weighed_at) ? $IS_WEIGHED : 0;
- $conclusion |= ($data['status'] == '已收件') ? $IS_RECEIVED : 0;
- $conclusion |= ($data['status'] == '派送中') ? $IS_SENDING : 0;//
- $conclusion |= ($last_routed_duration > $SHORT_RESPONSE_HOURS && $last_routed_duration < $LONG_RESPONSE_HOURS) ? $IS_SHORT_NO_RESPONSE : 0;
- $conclusion |= ($last_routed_duration > $LONG_RESPONSE_HOURS) ? $IS_LONG_NO_RESPONSE : 0;
- $conclusion |= ($last_routed_duration > $SENDING_RESPONSE_HOURS && $data['status'] == '派送中') ? $IS_SENDING_NO_RESPONSE : 0;
- return $conclusion;
- })();
- switch ($conclusion) {
- case $IS_IN_VALID_TIME:
- $data['exception_type'] = '疑似库内丢件';
- break;
- case $IS_IN_VALID_TIME | $IS_WEIGHED:
- $data['exception_type'] = '揽件异常';
- break;
- case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SHORT_NO_RESPONSE:
- case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SHORT_NO_RESPONSE | $IS_WEIGHED:
- $data['exception_type'] = '中转异常';
- break;
- case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_LONG_NO_RESPONSE:
- case $IS_ROUTED | $IS_IN_VALID_TIME | $IS_LONG_NO_RESPONSE | $IS_WEIGHED:
- $data['exception_type'] = '疑似丢件';
- break;
- default:
- break;
- }
- if($conclusion
- ==($conclusion | $IS_ROUTED | $IS_IN_VALID_TIME | $IS_SENDING | $IS_SENDING_NO_RESPONSE)){
- $data['exception_type'] = '派件异常';
- }
- switch ($conclusion) {
- case $IS_IN_VALID_TIME:
- case $IS_IN_VALID_TIME | $IS_WEIGHED:
- case $IS_ROUTED | $IS_SHORT_NO_RESPONSE:
- case $IS_LONG_NO_RESPONSE:
- $data['exception'] = '是';
- break;
- default:
- break;
- }
- return $data;
- }
- }
|