Forráskód Böngészése

跑腿计价接口请求方式变动:所有http并发请求

shizhongqi 8 hónapja
szülő
commit
f0980b6c79

+ 51 - 28
app-ghs/controllers/DeliveryController.php

@@ -9,7 +9,6 @@ use common\components\delivery\services\adapter\HuolalaAdapter;
 use common\components\util;
 use common\components\delivery\services\DispatchService;
 use ghs\models\delivery\CancelOrderForm;
-use ghs\models\delivery\CreateOrderForm;
 use ghs\models\delivery\OrderDetailForm;
 use Yii;
 
@@ -143,6 +142,17 @@ class DeliveryController extends BaseController
         util::success(['url'=>$authUrl]);
     }
 
+    public function actionCancelAuth()
+    {
+        $platform = Yii::$app->request->get('platform');
+        $ret = DeliveryAuthTokenClass::deleteByCondition(['mainId'=>$this->mainId, 'platform'=>$platform]);
+        if($ret > 0) {
+            util::success(['message'=>'取消授权成功']);
+        } else {
+            util::fail('取消授权失败');
+        }
+    }
+
     public function actionHuolalaVehicle()
     {
         $mainId = $this->mainId;
@@ -241,7 +251,7 @@ class DeliveryController extends BaseController
         $shop = ShopClass::getById($this->shopId);
 
         $ds = new DispatchService($this->mainId);
-        $platforms = $ds->getBestPlatformByPrice($order, $shop, $orderTime);
+        $platforms = $ds->getAllPlatformPrice($order, $shop, $orderTime);
 
         $deliveryList = [];
         foreach ($platforms['quotes'] as $item) {
@@ -279,23 +289,28 @@ class DeliveryController extends BaseController
                     ];
                     break;
                 case 'huolala':
-                    $deliveryList[] = [
-                        'name' => '货拉拉',
-                        'en_name' => 'huolala',
-                        'price' => isset($item['price_list']) ? $item['price_list'][0]['price_conditions'][0]['total_price'] : 0,
-                        'distance' => isset($item['price_list']) ? $item['price_list'][0]['distance_info']['distance_total'] : 0,
-                        'type' => $item['vehicle_type'],
-                        'order_vehicle_id' => isset($item['price_list']) ? $item['price_list'][0]['vehicle_info']['order_vehicle_id'] : 0,
-                        'city_id' => $item['city_id'],
-                        'city_info_revision' => $item['city_info_revision'],
-                        // 下单需要透传的数据
-                        'vehicle_std' => $item['vehicle_std'],
-                        'spec_req' => $item['spec_req'],
-                        'price_calculate_id' => isset($item['price_list']) ? $item['price_list'][0]['price_calculate_id'] : '',
-                        'price_item_encryption' => isset($item['price_list']) ? $item['price_list'][0]['price_conditions'][0]['price_item_encryption'] : '',
-                        'vehicle_attr' => isset($item['price_list']) ? $item['price_list'][0]['vehicle_info']['vehicle_attr'] : 0,
-                        'commodity_info' => isset($item['price_list']) ? $item['price_list'][0]['price_conditions'][0]['commodity_info'] : [],
-                    ];
+                    $deliveryList = [];
+                    foreach($item['price_info_list'] as $arr) {
+                        $priceInfo = $arr['calculate_price_info'];
+                        $vehicleType = $arr['vehicle_type'];
+                        $deliveryList[] = [
+                            'name' => '货拉拉' . ' (' . $vehicleType['_meta']['vehicle_type'] . ')',
+                            'en_name' => 'huolala',
+                            'price' => $priceInfo['price_conditions'][0]['price_info']['total_price'],
+                            'distance' => $priceInfo['distance_info']['distance_total'],
+                            'type' => $vehicleType['_meta']['vehicle_type'],
+                            'order_vehicle_id' => $vehicleType['order_vehicle_id'],
+                            'city_id' => $item['city_id'],
+                            'city_info_revision' => $vehicleType['city_info_revision'],
+                            // // 下单需要透传的数据
+                            'vehicle_std' => $vehicleType['vehicle_std'],
+                            'spec_req' => $vehicleType['spec_req'],
+                            'price_calculate_id' => $priceInfo['price_calculate_id'],
+                            'price_item_encryption' => $priceInfo['price_conditions'][0]['price_item_encryption'],
+                            'vehicle_attr' => $priceInfo['vehicle_info']['vehicle_attr'],
+                            'commodity_info' => $priceInfo['price_conditions'][0]['commodity_item'],
+                        ];
+                    }
                     break;
             }
         }
@@ -400,14 +415,16 @@ class DeliveryController extends BaseController
         $platform = $post['platform'] ?? '';
         $ghsOrderId = $post['ghsOrderId'];
 
+        // 先获取配送订单,确保 $gdo 始终可用
+        $gdo = GhsDeliveryOrderClass::getByCondition(['mainId'=>$this->mainId, 'ghsOrderId'=>$ghsOrderId, 'orderStatus!='=> -1], true);
+        if(empty($gdo)) {
+            util::fail('未找到对应订单: ' . $ghsOrderId);
+        }
+
+        // 如果没有提供平台和订单ID,从配送订单中获取
         if($platform == '' || $orderId == 0) {
-           $gdo = GhsDeliveryOrderClass::getByCondition(['mainId'=>$this->mainId, 'ghsOrderId'=>$ghsOrderId, 'orderStatus!='=>-1], true);
-           if(!empty($gdo)) {
-               $orderId = $gdo->orderId;
-               $platform = $gdo->deliveryId;
-           } else {
-               util::fail('未找到对应订单: ' . $ghsOrderId);
-           }
+           $orderId = $gdo->orderId;
+           $platform = $gdo->deliveryId;
         }
 
         $ds = new DispatchService($this->mainId, $platform);
@@ -429,13 +446,19 @@ class DeliveryController extends BaseController
             // 更新跑腿订单与批发订单状态
             $gdo->orderStatus = -1;
             $gdo->cancelCost = $cancelCostCent;
-            $gdo->save();
+            if(!$gdo->save()) {
+                Yii::error('保存配送订单失败: ' . json_encode($gdo->errors));
+                util::fail('保存配送订单失败');
+            }
 
             $ghsOrder = OrderClass::getById($ghsOrderId, true, 'id, sendDistance, sendStatus, deliveryId');
             $ghsOrder->sendDistance = 0;
             $ghsOrder->sendStatus = -4;
             $ghsOrder->deliveryId = '';
-            $ghsOrder->save();
+            if(!$ghsOrder->save()) {
+                Yii::error('保存批发订单失败: ' . json_encode($ghsOrder->errors));
+                util::fail('保存批发订单失败');
+            }
 
             util::success($ret['data'], "success");
         }

+ 10 - 2
biz-ghs/express/classes/DeliveryAuthTokenClass.php

@@ -6,6 +6,14 @@ use bizGhs\base\classes\BaseClass;
 class DeliveryAuthTokenClass extends BaseClass
 {
     //public static $baseFile = '\bizGhs\express\models\DeliveryAuthToken'; // DeliveryAuthToken::class;
-    // 通过 baseTable 指定表名,无需单独定义 AR
-    public static $baseTable = 'xhDeliveryAuthToken';
+    public static $baseFile = DeliveryAuthTokenAr::class;
 }
+
+// 在同文件末尾追加一个内联的 AR 类
+class DeliveryAuthTokenAR extends \bizGhs\base\models\Base
+{
+    public static function tableName()
+    {
+        return 'xhDeliveryAuthToken';
+    }
+}

+ 15 - 1
biz-ghs/express/classes/GhsDeliveryOrderClass.php

@@ -6,5 +6,19 @@ use bizGhs\base\classes\BaseClass;
 class GhsDeliveryOrderClass extends BaseClass
 {
     //public static $baseFile = '\bizGhs\express\models\GhsDelivery';
-    public static $baseTable = 'xhGhsDeliveryOrder';
+    public static $baseFile = GhsDeliveryOrderAR::class;
+}
+
+// 在同文件末尾追加一个内联的 AR 类
+class GhsDeliveryOrderAR extends \bizGhs\base\models\Base
+{
+    public static function tableName()
+    {
+        return 'xhGhsDeliveryOrder';
+    }
+}
+
+// 可选:为了兼容历史直接引用 models 的代码(如果存在),做一个别名映射
+if (!class_exists('\bizGhs\express\models\GhsDeliveryOrder')) {
+    class_alias(\bizGhs\express\classes\GhsDeliveryOrderAR::class, '\bizGhs\express\models\GhsDeliveryOrder');
 }

+ 11 - 44
common/base/classes/BaseClass.php

@@ -8,8 +8,6 @@ use \common\base\models\Base;
 class BaseClass
 {
     public static $baseFile; // 模型类名的路径字符串
-    public static $baseTable; // 表名(新增属性,表名和模型类名二选一,如果同时存在,则优先使用模型类名)
-    public static $baseDb; // 数据库组件ID
 
     /**
      * @return Base
@@ -24,52 +22,21 @@ class BaseClass
             }
         }
 
-        // 兼容:通过在 Class 上声明 static $baseTable 来动态指定表名
-        // 可选:再通过 static $baseDb 指定使用的 DB 组件 ID(如不设置则走默认)
-        if (property_exists(static::class, 'baseTable') && !empty(static::$baseTable)) {
-            $tableName = static::$baseTable;
-            $dbId = property_exists(static::class, 'baseDb') ? static::$baseDb : null;
-
-            $modelClass = new class extends \common\base\models\Base {
-                private static $_tableName = '';
-                private static $_dbId = null;
-
-                public function __construct($config = [])
-                {
-                    parent::__construct($config);
-                }
-
-                public static function tableName()
-                {
-                    return self::$_tableName;
-                }
-
-                public static function getDb()
-                {
-                    if (!empty(self::$_dbId)) {
-                        return \Yii::$app->get(self::$_dbId);
-                    }
-                    return parent::getDb();
-                }
-
-                public static function setTableConfig($tbl, $db)
-                {
-                    self::$_tableName = $tbl;
-                    self::$_dbId = $db;
-                }
-            };
-            
-            $modelClass->setTableConfig($tableName, $dbId);
-            
-            return $modelClass;
-        }
-
-        // 未设置 $baseFile / $baseTable,配置不完整
+        // 未设置 $baseFile,配置不完整
         throw new \yii\base\InvalidConfigException(
-            'Missing model binding for ' . static::class . ': set static $baseFile or static $baseTable.'
+            'Missing model binding for ' . static::class . ': set static $baseFile.'
         );
     }
 
+    /**
+     * 获取模型对象
+     * @return yii\db\ActiveRecord;
+     */
+    public static function getActiveRecord()
+    {
+        return self::getModel();
+    }
+
     //查询全部 ssh 2019.11.27
     public static function getAllList($select, $where, $order = '', $with = '')
     {

+ 14 - 9
common/components/delivery/helpers/HttpClient.php

@@ -198,8 +198,15 @@ class HttpClient
             return $results;
         }
 
+        $client = new Client([
+            'verify' => false,
+        ]);
+
         // 使用生成器生成请求和配置对
-        $requestGenerator = function () use ($requestConfigs) {
+        // Guzzle Pool 期望每个生成的值是:
+        // 1. 返回 Promise 的 callable
+        // 2. 或直接是 RequestInterface
+        $requestGenerator = function () use ($requestConfigs, $client) {
             foreach ($requestConfigs as $index => $config) {
                 $request = new Request(
                     $config['method'],
@@ -207,17 +214,15 @@ class HttpClient
                     $config['headers'],
                     $config['body']
                 );
-                // 返回 [request => options] 对,Guzzle 会应用这些选项
-                yield $request => [
-                    'timeout' => $config['timeout'],
-                ];
+                // 返回 callable,返回 Promise 对象
+                yield function () use ($client, $request, $config) {
+                    return $client->sendAsync($request, [
+                        'timeout' => $config['timeout'],
+                    ]);
+                };
             }
         };
 
-        $client = new Client([
-            'verify' => false,
-        ]);
-
         // 使用 Pool 实现并发请求
         $pool = new Pool($client, $requestGenerator(), [
             'concurrency' => $maxConcurrent,

+ 166 - 111
common/components/delivery/services/DispatchService.php

@@ -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;

+ 40 - 83
common/components/delivery/services/adapter/FengniaoAdapter.php

@@ -418,87 +418,13 @@ class FengniaoAdapter implements Adapter
     }
 
     /**
-     * 获取运费报价(预下单接口)
-     * 
-     * 根据蜂鸟官方文档实现报价功能,返回所有可选的商品列表
-     * 每项包含当前使用该商品下单时对应的价格等信息
+     * 构建运费报价请求信息(供并发请求使用)
      * 
-     * @param array $data 报价参数,结构参考:
-     *   [
-     *       'partner_order_code' => '外部订单号(必填,用于幂等)',
-     *       'chain_store_id' => '门店ID(门店发单时必填)',
-     *       'out_shop_code' => '外部门店ID',
-     *       'transport_address' => '取货点地址',
-     *       'transport_latitude' => '取货点纬度',
-     *       'transport_longitude' => '取货点经度',
-     *       'transport_tel' => '取货点联系人电话',
-     *       'position_source' => '经纬度来源(必填,1:腾讯, 2:百度, 3:高德,推荐使用3)',
-     *       'receiver_address' => '收货人地址(必填)',
-     *       'receiver_latitude' => '收货人纬度(必填)',
-     *       'receiver_longitude' => '收货人经度(必填)',
-     *       'goods_total_amount_cent' => '订单商品总金额(分,必填)',
-     *       'goods_actual_amount_cent' => '订单货物实付金额(分,必填)',
-     *       'goods_weight' => '货物总重量(kg,必填)',
-     *       'goods_count' => '货物件数(必填)',
-     *       'goods_item_list' => [  // 货物明细(必填)
-     *           [
-     *               'item_id' => '商品编号',
-     *               'item_name' => '商品名称',
-     *               'item_amount_cent' => '商品原价(分,必填)',
-     *               'item_actual_amount_cent' => '商品实际支付金额(分,必填)',
-     *               'item_size' => '商品尺寸(1:小, 2:中, 3:大)',
-     *               'item_quantity' => '商品数量',
-     *               'item_remark' => '商品备注',
-     *           ],
-     *       ],
-     *       'order_type' => '订单类型(必填,1:即时单, 3:预约单,默认1)',
-     *       'require_receive_time' => '需要送达时间(预约单时必填,毫秒级时间戳)',
-     *       'order_add_time' => '下单时间(毫秒)',
-     *       'order_tip_amount_cent' => '订单小费金额(分)',
-     *       'order_source' => '商户订单来源(0/不传:手发单, 2:美团, 4:口碑, 6:饿了么, 7:支付宝等)',
-     *       'order_source_order_id' => '商户订单来源单号',
-     *       'use_coupon' => '是否使用优惠券(0:否, 1:是,默认使用)',
-     *       'base_goods_id' => '基础商品ID',
-     *       'service_goods_id' => '服务商品ID',
-     *       'expect_fetch_time' => '期望取货时间',
-     *       'appoint_extra_goods_ids' => '指定增值商品ID列表',
-     *   ]
-     * @param  string  $orderTime 配送时间
-     * @return array|null 包含可用商品列表及报价信息的结果
-     *   [
-     *       'platform' => 'fengniao',
-     *       'distance' => 配送距离(米),
-     *       'time' => 预询系统时间戳(毫秒),
-     *       'goods_infos' => [  // 可用运力列表
-     *           [
-     *               'base_goods_id' => 基础商品ID,
-     *               'service_goods_id' => 服务商品ID,
-     *               't_index_id' => '预询标识(入单时需传入)',
-     *               'is_valid' => 是否可用 (0:不可用, 1:可用),
-     *               'disable_reason' => '不可用原因',
-     *               'slogan' => '商品介绍',
-     *               'actual_delivery_amount_cent' => 优惠后配送费总价格(分,最关键字段),
-     *               'total_delivery_amount_cent' => 原始配送费总价格(分),
-     *               'predict_delivery_minutes' => 预计送达时间(分钟),
-     *               'predict_delivery_time' => 预计送达时间(毫秒),
-     *               'can_add_tip' => 是否支持加小费 (0:否, 1:是),
-     *               'warehouse_id' => 优惠券记录ID,
-     *               'price_detail' => 价格明细 [
-     *                   'start_price_cent' => 起送价(分),
-     *                   'distance_price_cent' => 距离加价(分),
-     *                   'weight_price_cent' => 重量加价(分),
-     *                   'pressure_surcharge_cent' => 运力紧张加价(分),
-     *                   'time_period_surcharge_cent' => 时段加价(分),
-     *                   'river_crossing_surcharge_cent' => 跨江单加价(分),
-     *                   'category_surcharge_cent' => 品类加价(分),
-     *                   'temporary_surcharge_cent' => 临时加价(分),
-     *                   'order_price_surcharge_cent' => 客单价加价(分),
-     *               ],
-     *           ],
-     *       ],
-     *   ]
+     * @param array $data 报价参数
+     * @param string $orderTime 配送时间
+     * @return array|null 包含 url, payload, headers, timeout 的请求信息
      */
-    public function getPrice($data, $orderTime)
+    public function buildPriceRequest($data, $orderTime)
     {
         // 订单类型为预约单时必传,如需要送达时间 – 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单
         $timeDiff = strtotime($orderTime) - time();
@@ -609,11 +535,24 @@ class FengniaoAdapter implements Adapter
 
         // 构建请求参数
         $payload = $this->buildRequestPayload('preCreateOrder', $businessData);
-        // 发送请求
         $url = $this->baseUrl . '/preCreateOrder';
-        $resp = HttpClient::post($url, $payload, [
-            'Content-Type' => 'application/json',
-        ]);
+
+        return [
+            'url' => $url,
+            'data' => $payload,
+            'headers' => ['Content-Type' => 'application/json'],
+            'timeout' => 5.0,
+        ];
+    }
+
+    /**
+     * 处理运费报价响应
+     * 
+     * @param array $resp API 响应
+     * @return array|null 解析后的报价结果
+     */
+    public function processPriceResponse($resp)
+    {
         Yii::info("[FengniaoAdapter] getPrice Response: " . json_encode($resp));
 
         // 处理响应
@@ -676,6 +615,24 @@ class FengniaoAdapter implements Adapter
         return null;
     }
 
+    /**
+     * 获取运费报价(预下单接口)
+     * 
+     * 根据蜂鸟官方文档实现报价功能,返回所有可选的商品列表
+     * 每项包含当前使用该商品下单时对应的价格等信息
+     * 
+     * @param array $data 报价参数
+     * @param  string  $orderTime 配送时间
+     * @return array|null 包含可用商品列表及报价信息的结果
+     * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代
+     */
+    public function getPrice($data, $orderTime)
+    {
+        $request = $this->buildPriceRequest($data, $orderTime);
+        $resp = HttpClient::post($request['url'], $request['data'], $request['headers']);
+        return $this->processPriceResponse($resp);
+    }
+
     /**
      * 获取可用订单取消原因列表
      * 

+ 100 - 153
common/components/delivery/services/adapter/HuolalaAdapter.php

@@ -514,40 +514,13 @@ class HuolalaAdapter implements Adapter
     }
 
     /**
-     * 获取运费报价(多品类)- 需用户授权
-     * 
-     * 根据货拉拉官方文档 u-price-multiCalculate 实现多品类估价功能
+     * 构建多车型报价请求信息列表(供并发请求使用)
      * 
-     * @param array $data 估价参数,结构参考:
-     *   [
-     *       'city_id' => 下单城市ID (必需),
-     *       'order_vehicle_id' => 城市车型ID (必需),
-     *       'city_info_revision' => 城市版本号 (必需),
-     *       'order_time' => 用车时间戳(秒) (必需),
-     *       'addr_info' => 地址信息数组 (必需)
-     *           [
-     *               [
-     *                   'name' => 地址名称,
-     *                   'addr' => 地址详情,
-     *                   'city_id' => 城市ID,
-     *                   'city_name' => 城市名称,
-     *                   'district_name' => 区(县),
-     *                   'house_number' => 楼层及门牌号,
-     *                   'contacts_name' => 联系人,
-     *                   'contacts_phone_no' => 联系方式,
-     *                   'lat_lon' => ['lat' => 纬度, 'lon' => 经度]
-     *               ],
-     *               // ... 第一个是发货地址,最后一个是收货地址,中间是经停点
-     *           ],
-     *       'vehicle_std' => ['双排座'] 车型附加要求 (可选),
-     *       'spec_req' => [2,5] 城市额外需求 (可选),
-     *       'coupon_id' => 优惠券ID (可选),
-     *       'invoice_type' => 开票类型 (可选),
-     *       'order_service_type' => 服务类型 (可选),
-     *   ]
-     * @return array|null 包含多个计价方案的报价结果
+     * @param array $data 包含 vehicle_type_list 的数据
+     * @param string $orderTime 配送时间
+     * @return array 请求信息列表,以车型ID为键
      */
-    public function getPrice($data, $orderTime)
+    public function buildPriceRequests($data, $orderTime)
     {
         //用车时间 -- 用车时间(unix时间戳:秒)。 取值范围:(当前时间+10分钟)<=用车时间<=(当前时间+5天)
         $orderTime = strtotime($orderTime);
@@ -555,136 +528,110 @@ class HuolalaAdapter implements Adapter
             $orderTime = time() + 600;
         }
 
-        // 构建地址信息
-        $addrInfo = [];
-        if (!empty($data['addr_info']) && is_array($data['addr_info'])) {
-            foreach ($data['addr_info'] as $addr) {
-                $addrInfo[] = [
-                    'name' => $addr['name'] ?? '',
-                    'addr' => $addr['addr'] ?? '',
-                    'city_id' => (int)($addr['city_id'] ?? 0),
-                    'city_name' => $addr['city_name'] ?? '',
-                    'district_name' => $addr['district_name'] ?? '',
-                    'house_number' => $addr['house_number'] ?? '',
-                    'contacts_name' => $addr['contacts_name'] ?? '',
-                    'contacts_phone_no' => $addr['contacts_phone_no'] ?? '',
-                    'lat_lon' => [
-                        'lat' => (double)($addr['lat_lon']['lat'] ?? 0),
-                        'lon' => (double)($addr['lat_lon']['lon'] ?? 0),
-                    ]
-                ];
+        $requests = [];
+        foreach($data['vehicle_type_list'] as $vehicle) {
+            // 构建地址信息
+            $addrInfo = [];
+            if (!empty($vehicle['addr_info']) && is_array($vehicle['addr_info'])) {
+                foreach ($vehicle['addr_info'] as $addr) {
+                    $addrInfo[] = [
+                        'name' => $addr['name'] ?? '',
+                        'addr' => $addr['addr'] ?? '',
+                        'city_id' => (int)($addr['city_id'] ?? 0),
+                        'city_name' => $addr['city_name'] ?? '',
+                        'district_name' => $addr['district_name'] ?? '',
+                        'house_number' => $addr['house_number'] ?? '',
+                        'contacts_name' => $addr['contacts_name'] ?? '',
+                        'contacts_phone_no' => $addr['contacts_phone_no'] ?? '',
+                        'lat_lon' => [
+                            'lat' => (double)($addr['lat_lon']['lat'] ?? 0),
+                            'lon' => (double)($addr['lat_lon']['lon'] ?? 0),
+                        ]
+                    ];
+                }
+            }
+            
+            $apiData = [
+                'city_id' => (int)($data['city_id'] ?? 0),
+                'order_vehicle_id' => (int)($vehicle['order_vehicle_id'] ?? 0),
+                'city_info_revision' => (int)($vehicle['city_info_revision'] ?? 0),
+                'order_time' => $orderTime,
+                'addr_info' => $addrInfo,
+            ];
+            
+            if (!empty($vehicle['vehicle_std'])) {
+                $apiData['vehicle_std'] = is_array($vehicle['vehicle_std']) ? $vehicle['vehicle_std'] : [$vehicle['vehicle_std']];
+            }
+            if (!empty($vehicle['spec_req'])) {
+                $apiData['spec_req'] = is_array($vehicle['spec_req']) ? $vehicle['spec_req'] : [$vehicle['spec_req']];
+            }
+            if (isset($vehicle['coupon_id']) && !empty($vehicle['coupon_id'])) {
+                $apiData['coupon_id'] = $vehicle['coupon_id'];
             }
+            if (isset($vehicle['invoice_type'])) {
+                $apiData['invoice_type'] = (int)$vehicle['invoice_type'];
+            }
+            if (isset($vehicle['order_service_type'])) {
+                $apiData['order_service_type'] = (int)$vehicle['order_service_type'];
+            }
+            
+            $payload = $this->buildRequestPayload(
+                'u-price-multiCalculate',
+                $apiData,
+                $this->accessToken
+            );
+            
+            // 使用车型ID作为请求的唯一标识
+            $vehicleId = (int)($vehicle['order_vehicle_id'] ?? 0);
+            $requests["huolala_vehicle_{$vehicleId}"] = [
+                'url' => $this->baseUrl,
+                'data' => $payload,
+                'headers' => [],
+                'timeout' => 5.0,
+            ];
         }
 
-        // 构建业务数据
-        $apiData = [
-            'city_id' => (int)($data['city_id'] ?? 0),
-            'order_vehicle_id' => (int)($data['order_vehicle_id'] ?? 0),
-            'city_info_revision' => (int)($data['city_info_revision'] ?? 0),
-            'order_time' => $orderTime,
-            'addr_info' => $addrInfo,
-        ];
+        return $requests;
+    }
 
-        // 可选参数
-        if (!empty($data['vehicle_std'])) {
-            $apiData['vehicle_std'] = is_array($data['vehicle_std']) ? $data['vehicle_std'] : [$data['vehicle_std']];
-        }
-        if (!empty($data['spec_req'])) {
-            $apiData['spec_req'] = is_array($data['spec_req']) ? $data['spec_req'] : [$data['spec_req']];
-        }
-        if (isset($data['coupon_id']) && !empty($data['coupon_id'])) {
-            $apiData['coupon_id'] = $data['coupon_id'];
-        }
-        if (isset($data['invoice_type'])) {
-            $apiData['invoice_type'] = (int)$data['invoice_type'];
-        }
-        if (isset($data['order_service_type'])) {
-            $apiData['order_service_type'] = (int)$data['order_service_type'];
+    /**
+     * 处理货拉拉报价响应
+     * 
+     * @param array $resp API 响应
+     * @return array|null 解析后的报价结果
+     */
+    public function processPriceResponse($resp)
+    {
+        if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
+            return $resp['data'];
         }
+        return null;
+    }
 
-        // 构建请求参数
-        $payload = $this->buildRequestPayload(
-            'u-price-multiCalculate',
-            $apiData,
-            $this->accessToken
-        );
-
-        // 发送请求
-        $resp = HttpClient::post($this->baseUrl, $payload);
-
-        // 处理响应 - 返回多个计价方案
-        if (isset($resp['ret']) && is_array($resp['data'])) {
-            $priceList = [];
-            $calculatePriceInfoList = $resp['data']['calculate_price_info_list'];
-            foreach ($calculatePriceInfoList as $priceInfo) {
-                // 处理每个计价方案
-                $priceConditions = [];
-                if (!empty($priceInfo['price_conditions']) && is_array($priceInfo['price_conditions'])) {
-                    foreach ($priceInfo['price_conditions'] as $condition) {
-                        // 计算总价
-                        $totalPrice = 0;
-                        $priceItems = [];
-                        if (!empty($condition['price_item_list']) && is_array($condition['price_item_list'])) {
-                            foreach ($condition['price_item_list'] as $item) {
-                                $totalPrice += (int)($item['value_fen'] ?? 0);
-                                $priceItems[] = [
-                                    'type' => $item['type'] ?? 0,
-                                    'name' => $item['name'] ?? '',
-                                    'value_fen' => (int)($item['value_fen'] ?? 0),
-                                    'tax_value_fen' => (int)($item['tax_value_fen'] ?? 0),
-                                    'pre_value_fen' => (int)($item['pre_value_fen'] ?? 0),
-                                ];
-                            }
-                        }
-
-                        $priceConditions[] = [
-                            'price_item_encryption' => $condition['price_item_encryption'] ?? '',
-                            'total_price' => $totalPrice,
-                            'price_items' => $priceItems,
-                            'commodity_info' => [
-                                'code' => $condition['commodity_item']['code'] ?? '',
-                                'prd_code' => $condition['commodity_item']['prd_code'] ?? '',
-                                'biz_line' => $condition['commodity_item']['biz_line'] ?? '',
-                                'sub_biz_line' => $condition['commodity_item']['sub_biz_line'] ?? '',
-                                'arg_channel' => $condition['commodity_item']['arg_channel'] ?? '',
-                                'commodity_display_name' => $condition['commodity_item']['commodity_display_name'] ?? '',
-                                'sub_codes' => $condition['commodity_item']['sub_codes'] ?? [],
-                                'spec_req' => $condition['commodity_item']['spec_req'] ?? [],
-                            ],
-                            'coupon_info' => [
-                                'coupon_id' => $condition['coupon_info']['coupon_id'] ?? null,
-                                'coupon_value' => $condition['coupon_info']['coupon_value'] ?? 0,
-                                'discount_type' => $condition['coupon_info']['discount_type'] ?? null,
-                                'discount_amount' => $condition['coupon_info']['discount_amount'] ?? 0,
-                            ]
-                        ];
-                    }
-                }
-
-                $priceList[] = [
-                    'price_calculate_id' => $priceInfo['price_calculate_id'] ?? '',
-                    'vehicle_info' => [
-                        'order_vehicle_id' => (int)($priceInfo['vehicle_info']['order_vehicle_id'] ?? 0),
-                        'standard_order_vehicle_id' => (int)($priceInfo['vehicle_info']['standard_order_vehicle_id'] ?? 0),
-                        'order_vehicle_name' => $priceInfo['vehicle_info']['order_vehicle_name'] ?? '',
-                        'vehicle_attr' => (int)($priceInfo['vehicle_info']['vehicle_attr'] ?? 0),
-                    ],
-                    'distance_info' => [
-                        'distance_total' => (int)($priceInfo['distance_info']['distance_total'] ?? 0),
-                        'distance_by' => (int)($priceInfo['distance_info']['distance_by'] ?? 0),
-                        'duration' => (int)($priceInfo['distance_info']['duration'] ?? 0),
-                    ],
-                    'price_conditions' => $priceConditions,
-                ];
+    /**
+     * 获取运费报价(多品类)- 需用户授权
+     * 
+     * 根据货拉拉官方文档 u-price-multiCalculate 实现多品类估价功能
+     * 
+     * @param array $data 估价参数
+     * @param string $orderTime 配送时间
+     * @return array|null 包含多个计价方案的报价结果
+     * @deprecated 使用 buildPriceRequests 和 processPriceResponse 替代
+     */
+    public function getPrice($data, $orderTime)
+    {
+        $requests = $this->buildPriceRequests($data, $orderTime);
+        $priceList = [];
+        
+        foreach ($requests as $requestInfo) {
+            $resp = HttpClient::post($requestInfo['url'], $requestInfo['data']);
+            $processed = $this->processPriceResponse($resp);
+            if ($processed) {
+                $priceList[] = $processed;
             }
-
-            return [
-                'platform' => 'huolala',
-                'price_list' => $priceList,
-            ];
         }
 
-        return null;
+        return !empty($priceList) ? ['price_list' => $priceList] : null;
     }
 
     /**

+ 37 - 43
common/components/delivery/services/adapter/ShansongAdapter.php

@@ -95,47 +95,13 @@ class ShansongAdapter implements Adapter
     }
 
     /**
-     * * 下单询价 - 订单计费接口
-     * 根据闪送官方文档 /openapi/developer/v5/orderCalculate
-     *
-     * @param $data
-     * @param $orderTime
-     * 入参 $data 结构参考:
-     * [
-     *     'city_name' => '北京市',
-     *     'sender' => [
-     *         'from_address' => '东升科技国际园',
-     *         'from_address_detail' => '2层202',
-     *         'from_sender_name' => '小闪',
-     *         'from_mobile' => '13800000000',
-     *         'from_latitude' => '40.047858',
-     *         'from_longitude' => '116.378424',
-     *     ],
-     *     'receiver_list' => [
-     *         [
-     *             'order_no' => 'C1119A000013053981',
-     *             'to_address' => '永泰庄地铁站',
-     *             'to_address_detail' => '1楼',
-     *             'to_receiver_name' => '小送',
-     *             'to_mobile' => '13800000001',
-     *             'to_latitude' => '40.043612',
-     *             'to_longitude' => '116.361199',
-     *             'good_type' => 5,           // 物品类型
-     *             'weight' => 2,              // 物品重量(kg,整数)
-     *             'remarks' => '',            // 备注
-     *         ]
-     *     ],
-     *     'appoint_type' => 0,              // 0: 立即单,1: 预约单
-     *     'appointment_date' => '',         // 预约时间 yyyy-MM-dd HH:mm
-     *     'store_id' => 393549,             // 店铺ID
-     *     'travel_way' => 0,                // 指定交通工具,0: 不限交通方式
-     *     'delivery_type' => 1,             // 1: 帮我送,2: 帮我取
-     *     'expect_start_time' => null,      // 期望送达时间起始(毫秒级时间戳)
-     *     'expect_end_time' => null,        // 期望送达时间终止(毫秒级时间戳)
-     * ]
-     * @return array|null
+     * 构建订单计费请求信息(供并发请求使用)
+     * 
+     * @param array $data 订单数据
+     * @param string $orderTime 配送时间
+     * @return array 包含 url, data, headers, timeout 的请求信息
      */
-    public function getPrice($data, $orderTime)
+    public function buildPriceRequest($data, $orderTime)
     {
         // 预约类型
         $timeDiff = strtotime($orderTime) - time();
@@ -199,7 +165,6 @@ class ShansongAdapter implements Adapter
             $requestData['appointmentDate'] = $orderTime;
         }
         $requestData = $this->filterEmptyValues($requestData);
-        //$requestData = $this->sortArrayRecursive($requestData);
         $requestData = json_encode($requestData, JSON_UNESCAPED_UNICODE);
 
         // 构建签名用的完整payload
@@ -214,9 +179,22 @@ class ShansongAdapter implements Adapter
         $sign = SignHelper::makeSign($payload, $this->appSecret, 'md5', true, 'shansong');
         $payload['sign'] = $sign;
 
-        // 发送请求到计费接口
-        $resp = HttpClient::post("{$this->baseUrl}/openapi/developer/v5/orderCalculate", $payload);
+        return [
+            'url' => "{$this->baseUrl}/openapi/developer/v5/orderCalculate",
+            'data' => $payload,
+            'headers' => [],
+            'timeout' => 5.0,
+        ];
+    }
 
+    /**
+     * 处理闪送报价响应
+     * 
+     * @param array $resp API 响应
+     * @return array|null 解析后的报价结果
+     */
+    public function processPriceResponse($resp)
+    {
         // 处理响应
         if (isset($resp['status']) && $resp['status'] == 200 && isset($resp['data'])) {
             $respData = $resp['data'];
@@ -239,6 +217,22 @@ class ShansongAdapter implements Adapter
         return null;
     }
 
+    /**
+     * * 下单询价 - 订单计费接口
+     * 根据闪送官方文档 /openapi/developer/v5/orderCalculate
+     *
+     * @param $data
+     * @param $orderTime
+     * @return array|null
+     * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代
+     */
+    public function getPrice($data, $orderTime)
+    {
+        $request = $this->buildPriceRequest($data, $orderTime);
+        $resp = HttpClient::post($request['url'], $request['data']);
+        return $this->processPriceResponse($resp);
+    }
+
     public function selectOrder($orderId, $thirdOrderNo)
     {
         // 构建请求体