LogisticSFService.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. namespace App\Services;
  3. use Exception;
  4. use Illuminate\Http\Client\Response;
  5. use Illuminate\Support\Facades\Http;
  6. class LogisticSFService
  7. {
  8. /**
  9. * 顺丰字段与数据库字段的映射关心
  10. * @var string[]
  11. */
  12. protected $protected_switch = [
  13. 'logistic_number' => 'mailno',
  14. 'transfer_status' => 'remark',
  15. 'received_at' => 'accept_time',
  16. ];
  17. /**
  18. * 获取顺丰快递揽收数据
  19. * @param array $logisticNums [logisticNums]快递单号数组
  20. * @return array 快递揽收信息数组
  21. * @throws Exception 未知的丰桥opCode
  22. */
  23. public function get(array $logisticNums): array
  24. {
  25. // 将$logisticNums以10个位单位进行分割,返回二维数组
  26. $logisticNums_size_10 = array_chunk($logisticNums, config('api_logistic.SF.max_size', 10));
  27. // 遍历二维数组批量查询顺丰接口
  28. //将查询到的结果整合到一起,更新order_packages.received_at
  29. $result = [];
  30. foreach ($logisticNums_size_10 as $numbers) {
  31. $numbersStr = implode(',', $numbers);
  32. $result_10 = $this->getResultFromSF(config('api_logistic.SF.head'), $numbersStr, config('api_logistic.SF.check_word'), config('api_logistic.SF.url'));
  33. $result = array_merge($result, $result_10);
  34. }
  35. return $result;
  36. }
  37. /**
  38. * @param string $head 客户号
  39. * @param string $numbers 快递单号字符串,多个以','分隔
  40. * @param string $checkWord 客户秘钥
  41. * @param string $url 顺丰接口地址
  42. * @return array
  43. * @throws Exception 未知的丰桥opCode
  44. */
  45. public function getResultFromSF(string $head, string $numbers, string $checkWord, string $url): array
  46. {
  47. $responseBody = get_object_vars(simplexml_load_string($this->sendHttpToSF($head, $numbers, $checkWord, $url))->Body)['RouteResponse'];
  48. $result = [];
  49. if (is_array($responseBody)) {//SF返回多个单号的查询结果
  50. $result = $this->transformSFMoreToArr($responseBody, $result);
  51. } else {
  52. $result[] = $this->transformSFOneToArr(get_object_vars($responseBody), []);
  53. }
  54. return $result;
  55. }
  56. /**
  57. * 构建顺丰xml请求体
  58. * @param string $head
  59. * @param string $number
  60. * @return string
  61. */
  62. private function buildXmlStr(string $head, string $number): string
  63. {
  64. return <<<xml
  65. <?xml version="1.0" encoding="utf-8" ?>
  66. <Request service='RouteService' lang='zh-CN'>
  67. <Head>$head</Head>
  68. <Body>
  69. <RouteRequest
  70. tracking_number="{$number}" tracking_type='1' method_type='1'
  71. />
  72. </Body>
  73. </Request>
  74. xml;
  75. }
  76. /**
  77. * 将单个单号的顺丰数据转换为数组
  78. * @param array $routeResponse
  79. * @param array $data
  80. * @return array
  81. * @throws Exception
  82. */
  83. public function transformSFOneToArr(array $routeResponse, array $data): array
  84. {
  85. $data['logistic_number'] = $routeResponse['@attributes'][$this->protected_switch['logistic_number']];
  86. $lastRoute = get_object_vars($routeResponse['Route'][count($routeResponse['Route']) - 1])['@attributes'];//获取最新的路由信息
  87. $data = $this->switchOpCodeToStatus($lastRoute, $data);
  88. $data['transfer_status'] = $this->transformRoutes($routeResponse['Route']);
  89. return $data;
  90. }
  91. /**
  92. * 转换快递路由信息
  93. * @param array $routs 快递路由
  94. * @return array
  95. */
  96. public function transformRoutes(array $routs): array
  97. {
  98. $result = [];
  99. foreach ($routs as $route) {
  100. $route = get_object_vars($route)['@attributes'];
  101. $data['accept_time'] = $route['accept_time'];
  102. $data['accept_address'] = $route['accept_address'];
  103. $data['remark'] = $route['remark'];
  104. $result[] = $data;
  105. }
  106. return $result;
  107. }
  108. /**
  109. * 将最新路由信息转换为数组
  110. * @param array $lastRoute
  111. * @param array $data
  112. * @return array
  113. * @throws Exception
  114. */
  115. public function switchOpCodeToStatus(array $lastRoute, array $data): array
  116. {
  117. switch ($lastRoute['opcode']) {
  118. case 123:
  119. case 130:
  120. case 3036:
  121. case 31:
  122. case 30:
  123. $data['status'] = '在途';
  124. break;
  125. case 70:
  126. case 33:
  127. $data['status'] = '派送异常';
  128. $data['exception'] = '是';
  129. break;
  130. case 204:
  131. case 44:
  132. $data['status'] = '派送中';
  133. break;
  134. case 50:
  135. $data['status'] = '已揽收';
  136. break;
  137. case 607:
  138. case 8000:
  139. case 80:
  140. $data['status'] = '已收件';
  141. $data['received_at'] = $lastRoute[$this->protected_switch['received_at']];
  142. break;
  143. case 648:
  144. case 99:
  145. $data['status'] = '返回中';
  146. break;
  147. default:
  148. throw new Exception("未知的丰桥状态码: " . $lastRoute['opcode'] . '->' . json_encode($lastRoute));
  149. }
  150. return $data;
  151. }
  152. /**
  153. * @param string $head
  154. * @param string $numbers
  155. * @param string $checkWord
  156. * @param string $url
  157. * @return Response
  158. * @throws Exception
  159. */
  160. public function sendHttpToSF(string $head, string $numbers, string $checkWord, string $url): Response
  161. {
  162. $xml = $this->buildXmlStr($head, $numbers);
  163. $checkingJson = $xml . $checkWord;
  164. $verifyCode = base64_encode(md5($checkingJson, true));
  165. try {
  166. $response = Http::withHeaders(['Content-Type' => 'text/xml'])->get($url, ['xml' => $xml, 'verifyCode' => $verifyCode]);
  167. } catch (Exception $e) {
  168. throw new Exception("HTTP请求顺丰接口异常" . '->' . $e->getMessage());
  169. }
  170. return $response;
  171. }
  172. /**
  173. * 将多个顺丰的单号转换为数组
  174. * @param array $responseBody
  175. * @param array $result
  176. * @return array
  177. * @throws Exception
  178. */
  179. public function transformSFMoreToArr(array $responseBody, array $result): array
  180. {
  181. foreach ($responseBody as $routeResponse) {
  182. $result[] = $this->transformSFOneToArr(get_object_vars($routeResponse), []);
  183. }
  184. return $result;
  185. }
  186. }