LogisticSFService.php 8.4 KB

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