LogisticSFService.php 7.2 KB

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