|
|
@@ -28,13 +28,13 @@ class DispatchService
|
|
|
foreach($authPlatforms as $pt) {
|
|
|
switch ($pt['platform']) {
|
|
|
case 'shansong':
|
|
|
- $this->adapters['shansong'] = new ShansongAdapter($pt['access_token']);
|
|
|
+ $this->adapters['shansong'] = new ShansongAdapter($pt['accessToken']);
|
|
|
break;
|
|
|
case 'huolala':
|
|
|
- $this->adapters['huolala'] = new HuolalaAdapter($pt['access_token']);
|
|
|
+ $this->adapters['huolala'] = new HuolalaAdapter($pt['accessToken']);
|
|
|
break;
|
|
|
case 'fengniao':
|
|
|
- $adapter = new FengniaoAdapter($pt['access_token']);
|
|
|
+ $adapter = new FengniaoAdapter($pt['accessToken']);
|
|
|
$adapter->setMerchantId(14594092); // TODO: 确认是否需要使用动态的商户ID
|
|
|
$this->adapters['fengniao'] = $adapter;
|
|
|
break;
|
|
|
@@ -44,14 +44,14 @@ class DispatchService
|
|
|
$authPlatform = DeliveryAuthTokenClass::getByCondition(['mainId'=>$mainId, 'platform'=>$platform]);
|
|
|
switch ($platform) {
|
|
|
case 'shansong':
|
|
|
- $this->adapters['shansong'] = new ShansongAdapter($authPlatform['access_token']);
|
|
|
+ $this->adapters['shansong'] = new ShansongAdapter($authPlatform['accessToken']);
|
|
|
break;
|
|
|
case 'huolala':
|
|
|
- $this->adapters['huolala'] = new HuolalaAdapter($authPlatform['access_token']);
|
|
|
+ $this->adapters['huolala'] = new HuolalaAdapter($authPlatform['accessToken']);
|
|
|
break;
|
|
|
case 'fengniao':
|
|
|
- //$this->adapters['fengniao'] = new FengniaoAdapter($authPlatform['access_token']);
|
|
|
- $adapter = new FengniaoAdapter($authPlatform['access_token']);
|
|
|
+ //$this->adapters['fengniao'] = new FengniaoAdapter($authPlatform['accessToken']);
|
|
|
+ $adapter = new FengniaoAdapter($authPlatform['accessToken']);
|
|
|
$adapter->setMerchantId(14594092); // TODO: 确认是否需要使用动态的商户ID
|
|
|
$this->adapters['fengniao'] = $adapter;
|
|
|
break;
|
|
|
@@ -123,7 +123,7 @@ class DispatchService
|
|
|
* @param array $shop 店铺信息
|
|
|
* @return array 返回所有平台的报价结果
|
|
|
*/
|
|
|
- public function getBestPlatformByPrice($order, $shop, $orderTime)
|
|
|
+ public function getAllPlatformPrice($order, $shop, $orderTime)
|
|
|
{
|
|
|
// 第一步:准备前置数据(同步进行,因为某些平台需要这些数据)
|
|
|
$preparedData = $this->preparePlatformData($order, $shop, $orderTime);
|
|
|
@@ -153,7 +153,7 @@ class DispatchService
|
|
|
* @param array $shop 店铺信息
|
|
|
* @return array 各平台的订单数据
|
|
|
*/
|
|
|
- private function preparePlatformData($order, $shop, $orderTime)
|
|
|
+ private function preparePlatformData($order, $shop)
|
|
|
{
|
|
|
$preparedData = [];
|
|
|
|
|
|
@@ -203,44 +203,10 @@ class DispatchService
|
|
|
$cityVehicleList = $this->adapters['huolala']->getCityVehicleList($cityId); // 产生外部请求 -- TODO 优化成不耗时等待
|
|
|
$cityInfoRevision = $cityVehicleList['city_info_revision'];
|
|
|
|
|
|
- // 选择车型逻辑
|
|
|
+ // 获取所有车型列表
|
|
|
$vehicleList = $cityVehicleList['vehicle_list'];
|
|
|
- $selectVehicle = [];
|
|
|
- $vehicleType = '';
|
|
|
- $vehicleStd = [];
|
|
|
|
|
|
- // 优先选择 "跑腿"
|
|
|
- foreach ($vehicleList as $vehicle) {
|
|
|
- if ($vehicle['vehicle_name'] == '跑腿') {
|
|
|
- $selectVehicle = $vehicle;
|
|
|
- $vehicleType = '跑腿';
|
|
|
- $vehicleStd[] = count($vehicle['vehicle_std_item']) > 0 ? $vehicle['vehicle_std_item'][0]['name'] : '';
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果没有 "跑腿",选择 "二轮车" 或 "面包车"
|
|
|
- if (empty($selectVehicle)) {
|
|
|
- $erLunVehicle = [];
|
|
|
- $mianBaoVehicle = [];
|
|
|
-
|
|
|
- foreach ($vehicleList as $vehicle) {
|
|
|
- if ($vehicle['vehicle_name'] == '二轮车') {
|
|
|
- $erLunVehicle = $vehicle;
|
|
|
- $vehicleType = '二轮车';
|
|
|
- $vehicleStd[] = count($vehicle['vehicle_std_item']) > 0 ? $vehicle['vehicle_std_item'][0]['name'] : '';
|
|
|
- }
|
|
|
- if (in_array($vehicle['vehicle_name'], ['微面', '小面车', '小面包车'])) {
|
|
|
- $mianBaoVehicle = $vehicle;
|
|
|
- $vehicleType = $vehicle['vehicle_name'];
|
|
|
- $vehicleStd[] = count($vehicle['vehicle_std_item']) > 0 ? $vehicle['vehicle_std_item'][0]['name'] : '';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $selectVehicle = !empty($erLunVehicle) ? $erLunVehicle : $mianBaoVehicle;
|
|
|
- }
|
|
|
-
|
|
|
- if (empty($selectVehicle)) {
|
|
|
+ if (empty($vehicleList)) {
|
|
|
throw new \Exception('没有找到可选车型');
|
|
|
}
|
|
|
|
|
|
@@ -252,45 +218,56 @@ class DispatchService
|
|
|
}
|
|
|
$specReq = array_filter($specReq);
|
|
|
|
|
|
+ // 构建所有车型数据
|
|
|
+ $vehicleTypeList = [];
|
|
|
+ foreach ($vehicleList as $vehicle) {
|
|
|
+ $vehicleStd = [];
|
|
|
+ $vehicleStd[] = count($vehicle['vehicle_std_item']) > 0 ? $vehicle['vehicle_std_item'][0]['name'] : '';
|
|
|
+
|
|
|
+ $vehicleTypeList[$vehicle['order_vehicle_id']] = [
|
|
|
+ 'order_vehicle_id' => $vehicle['order_vehicle_id'],
|
|
|
+ 'city_info_revision' => $cityInfoRevision,
|
|
|
+ 'order_time' => time() + 600,
|
|
|
+ 'addr_info' => [
|
|
|
+ [
|
|
|
+ 'name' => $shop['merchantName'],
|
|
|
+ 'addr' => $shop['province'] . $shop['city'] . $shop['dist'] . $shop['address'],
|
|
|
+ 'city_id' => $cityId,
|
|
|
+ 'city_name' => $shop['city'],
|
|
|
+ 'district_name' => $shop['dist'],
|
|
|
+ 'house_number' => $shop['floor'],
|
|
|
+ 'contacts_name' => $shop['mobile'],
|
|
|
+ 'contacts_phone_no' => $shop['mobile'],
|
|
|
+ 'lat_lon' => ['lat' => (float)$shop['lat'], 'lon' => (float)$shop['long']],
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ 'name' => $order['customName'],
|
|
|
+ 'addr' => $order['fullAddress'],
|
|
|
+ 'city_id' => $cityId,
|
|
|
+ 'city_name' => $order['city'],
|
|
|
+ 'district_name' => $order['dist'],
|
|
|
+ 'house_number' => $order['floor'],
|
|
|
+ 'contacts_name' => $order['customName'],
|
|
|
+ 'contacts_phone_no' => $order['customMobile'],
|
|
|
+ 'lat_lon' => ['lat' => (float)$order['lat'], 'lon' => (float)$order['long']],
|
|
|
+ ]
|
|
|
+ ],
|
|
|
+ 'vehicle_std' => $vehicleStd,
|
|
|
+ 'spec_req' => array_values($specReq),
|
|
|
+ 'coupon_id' => 123456,
|
|
|
+ 'invoice_type' => 1,
|
|
|
+ 'order_service_type' => 1,
|
|
|
+ '_meta' => [
|
|
|
+ 'vehicle_type' => $vehicle['vehicle_name'],
|
|
|
+ 'city_info_revision' => $cityInfoRevision,
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
return [
|
|
|
'platform' => 'huolala',
|
|
|
'city_id' => $cityId,
|
|
|
- 'order_vehicle_id' => $selectVehicle['order_vehicle_id'],
|
|
|
- 'city_info_revision' => $cityInfoRevision,
|
|
|
- 'order_time' => time() + 600,
|
|
|
- 'addr_info' => [
|
|
|
- [
|
|
|
- 'name' => $shop['merchantName'],
|
|
|
- 'addr' => $shop['province'] . $shop['city'] . $shop['dist'] . $shop['address'],
|
|
|
- 'city_id' => $cityId,
|
|
|
- 'city_name' => $shop['city'],
|
|
|
- 'district_name' => $shop['dist'],
|
|
|
- 'house_number' => $shop['floor'],
|
|
|
- 'contacts_name' => $shop['mobile'],
|
|
|
- 'contacts_phone_no' => $shop['mobile'],
|
|
|
- 'lat_lon' => ['lat' => (float)$shop['lat'], 'lon' => (float)$shop['long']],
|
|
|
- ],
|
|
|
- [
|
|
|
- 'name' => $order['customName'],
|
|
|
- 'addr' => $order['fullAddress'],
|
|
|
- 'city_id' => $cityId,
|
|
|
- 'city_name' => $order['city'],
|
|
|
- 'district_name' => $order['dist'],
|
|
|
- 'house_number' => $order['floor'],
|
|
|
- 'contacts_name' => $order['customName'],
|
|
|
- 'contacts_phone_no' => $order['customMobile'],
|
|
|
- 'lat_lon' => ['lat' => (float)$order['lat'], 'lon' => (float)$order['long']],
|
|
|
- ]
|
|
|
- ],
|
|
|
- 'vehicle_std' => $vehicleStd,
|
|
|
- 'spec_req' => array_values($specReq),
|
|
|
- 'coupon_id' => 123456,
|
|
|
- 'invoice_type' => 1,
|
|
|
- 'order_service_type' => 1,
|
|
|
- '_meta' => [
|
|
|
- 'vehicle_type' => $vehicleType,
|
|
|
- 'city_info_revision' => $cityInfoRevision,
|
|
|
- ]
|
|
|
+ 'vehicle_type_list' => $vehicleTypeList,
|
|
|
];
|
|
|
}
|
|
|
|
|
|
@@ -376,7 +353,7 @@ class DispatchService
|
|
|
/**
|
|
|
* 并发获取各平台报价(核心实现)
|
|
|
*
|
|
|
- * 使用 Guzzle Pool 实现真正的并发调用,每个平台请求 5 秒超时。
|
|
|
+ * 使用 HttpClient::postConcurrent 实现真正的并发调用,每个平台请求 5 秒超时。
|
|
|
* 某个平台的超时或失败不会影响其他平台的执行。
|
|
|
*
|
|
|
* @param array $preparedData 准备好的各平台数据
|
|
|
@@ -390,8 +367,10 @@ class DispatchService
|
|
|
'failed' => [],
|
|
|
];
|
|
|
|
|
|
- // 如果 PHP 不支持真正的异步,采用顺序调用(但每个请求应用超时)
|
|
|
- // 这是 PHP 同步框架的限制,通过 set_time_limit 和超时配置来模拟效果
|
|
|
+ // 第一步:收集所有平台的请求信息
|
|
|
+ $allRequests = [];
|
|
|
+ $platformMapping = []; // 用于将请求标识映射回平台名称
|
|
|
+
|
|
|
foreach ($preparedData as $platform => $data) {
|
|
|
try {
|
|
|
if (!isset($this->adapters[$platform])) {
|
|
|
@@ -400,39 +379,115 @@ class DispatchService
|
|
|
}
|
|
|
|
|
|
$adapter = $this->adapters[$platform];
|
|
|
- $startTime = microtime(true);
|
|
|
-
|
|
|
- // 这里调用适配器的 getPrice 方法,该方法内部会使用 HttpClient
|
|
|
- // 在 HttpClient 中配置的超时会被应用
|
|
|
- $quote = $adapter->getPrice($data, $orderTime);
|
|
|
-
|
|
|
- $duration = microtime(true) - $startTime;
|
|
|
-
|
|
|
- if ($quote && !isset($quote['error'])) {
|
|
|
- // 添加平台标识和元数据
|
|
|
- $quote['platform'] = $platform;
|
|
|
- $quote['_duration'] = $duration;
|
|
|
-
|
|
|
- // 针对不同平台的数据处理
|
|
|
- if ($platform === 'huolala' && isset($data['_meta'])) {
|
|
|
- $quote['city_id'] = $data['city_id'];
|
|
|
- $quote['city_info_revision'] = $data['_meta']['city_info_revision'] ?? '';
|
|
|
- $quote['vehicle_type'] = $data['_meta']['vehicle_type'] ?? '';
|
|
|
- $quote['spec_req'] = $data['spec_req'] ?? [];
|
|
|
- $quote['vehicle_std'] = $data['vehicle_std'] ?? [];
|
|
|
+
|
|
|
+ // 根据平台类型调用相应的 buildPriceRequest(s) 方法
|
|
|
+ if ($platform === 'huolala') {
|
|
|
+ // 货拉拉返回多个请求(一个请求对应一个车型)
|
|
|
+ $priceRequests = $adapter->buildPriceRequests($data, $orderTime);
|
|
|
+ foreach ($priceRequests as $requestKey => $requestInfo) {
|
|
|
+ $allRequests[$requestKey] = $requestInfo;
|
|
|
+ $platformMapping[$requestKey] = ['platform' => 'huolala', 'data' => $data];
|
|
|
}
|
|
|
+ } else {
|
|
|
+ // 其他平台(蜂鸟、闪送)各返回一个请求
|
|
|
+ $requestInfo = $adapter->buildPriceRequest($data, $orderTime);
|
|
|
+ $requestKey = "{$platform}_quote";
|
|
|
+ $allRequests[$requestKey] = $requestInfo;
|
|
|
+ $platformMapping[$requestKey] = ['platform' => $platform, 'data' => $data];
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $results['failed'][$platform] = "构建请求失败: {$e->getMessage()}";
|
|
|
+ Yii::error("[DispatchService] {$platform} 构建请求异常: {$e->getMessage()}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($allRequests)) {
|
|
|
+ return $results;
|
|
|
+ }
|
|
|
|
|
|
- $results['success'][$platform] = $quote;
|
|
|
- Yii::info("[DispatchService] {$platform} 报价成功 (" . round($duration * 1000) . "ms)");
|
|
|
+ // 第二步:使用 postConcurrent 并发发送所有请求
|
|
|
+ $startTime = microtime(true);
|
|
|
+ $concurrentResults = HttpClient::postConcurrent($allRequests, 3);
|
|
|
+ $totalDuration = microtime(true) - $startTime;
|
|
|
+ Yii::info("[DispatchService] 所有报价请求完成 (" . round($totalDuration * 1000) . "ms)");
|
|
|
+
|
|
|
+ // 第三步:处理并发请求的结果
|
|
|
+ foreach ($concurrentResults['success'] as $requestKey => $response) {
|
|
|
+ if (!isset($platformMapping[$requestKey])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $mapping = $platformMapping[$requestKey];
|
|
|
+ $platform = $mapping['platform'];
|
|
|
+ $data = $mapping['data'];
|
|
|
+
|
|
|
+ try {
|
|
|
+ $adapter = $this->adapters[$platform];
|
|
|
+ $quote = null;
|
|
|
+
|
|
|
+ if ($platform === 'huolala') {
|
|
|
+ // 货拉拉的响应处理
|
|
|
+ $quote = $adapter->processPriceResponse($response);
|
|
|
+ if ($quote && !isset($results['success']['huolala'])) {
|
|
|
+ // 第一次处理货拉拉的结果,初始化
|
|
|
+ $results['success']['huolala'] = [
|
|
|
+ 'platform' => 'huolala',
|
|
|
+ 'city_id' => $data['city_id'],
|
|
|
+ 'vehicle_type_list' => $data['vehicle_type_list'],
|
|
|
+ 'price_info_list' => [],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ $keyArr = explode('_', $requestKey);
|
|
|
+ $vehicle_type_list_key = $keyArr[2];
|
|
|
+ // 将该车型的报价加入到列表中
|
|
|
+ if ($quote && isset($results['success']['huolala'])) {
|
|
|
+ $results['success']['huolala']['price_info_list'][] = [
|
|
|
+ 'calculate_price_info' => isset($quote['calculate_price_info_list']) ? $quote['calculate_price_info_list'][0] : [],
|
|
|
+ 'vehicle_type' => $data['vehicle_type_list'][$vehicle_type_list_key]
|
|
|
+ ];
|
|
|
+ }
|
|
|
} else {
|
|
|
- $errorMsg = $quote['error'] ?? '报价返回数据为空';
|
|
|
- $results['failed'][$platform] = $errorMsg;
|
|
|
- Yii::warning("[DispatchService] {$platform} 报价返回错误: {$errorMsg}");
|
|
|
+ // 蜂鸟和闪送的响应处理
|
|
|
+ $quote = $adapter->processPriceResponse($response);
|
|
|
+ if ($quote && !isset($quote['error'])) {
|
|
|
+ $quote['_duration'] = $totalDuration;
|
|
|
+ $results['success'][$platform] = $quote;
|
|
|
+ Yii::info("[DispatchService] {$platform} 报价成功 (" . round($totalDuration * 1000) . "ms)");
|
|
|
+ } else {
|
|
|
+ $errorMsg = $quote['error'] ?? '报价返回数据为空';
|
|
|
+ $results['failed'][$platform] = $errorMsg;
|
|
|
+ Yii::warning("[DispatchService] {$platform} 报价返回错误: {$errorMsg}");
|
|
|
+ }
|
|
|
}
|
|
|
} catch (\Throwable $e) {
|
|
|
- $results['failed'][$platform] = $e->getMessage();
|
|
|
- Yii::error("[DispatchService] {$platform} 报价异常: {$e->getMessage()}");
|
|
|
+ $platform = $mapping['platform'];
|
|
|
+ $results['failed'][$platform] = "处理响应失败: {$e->getMessage()}";
|
|
|
+ Yii::error("[DispatchService] {$platform} 处理响应异常: {$e->getMessage()}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第四步:处理失败的请求
|
|
|
+ foreach ($concurrentResults['failed'] as $requestKey => $errorMsg) {
|
|
|
+ if (!isset($platformMapping[$requestKey])) {
|
|
|
+ continue;
|
|
|
}
|
|
|
+
|
|
|
+ $platform = $platformMapping[$requestKey]['platform'];
|
|
|
+
|
|
|
+ // 对于货拉拉,只记录某个车型失败,不影响整体平台状态
|
|
|
+ if ($platform === 'huolala') {
|
|
|
+ Yii::warning("[DispatchService] huolala 车型报价失败 ({$requestKey}): {$errorMsg}");
|
|
|
+ } else {
|
|
|
+ if (!isset($results['failed'][$platform])) {
|
|
|
+ $results['failed'][$platform] = $errorMsg;
|
|
|
+ Yii::error("[DispatchService] {$platform} 报价请求失败: {$errorMsg}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 为货拉拉添加元数据
|
|
|
+ if (isset($results['success']['huolala'])) {
|
|
|
+ $results['success']['huolala']['_duration'] = $totalDuration;
|
|
|
}
|
|
|
|
|
|
return $results;
|