|  | @@ -15,6 +15,13 @@ class AiMeasurement extends Api
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      protected $noNeedLogin = [];
 | 
	
		
			
				|  |  |      protected $noNeedRight = '*';
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    // 第三方AI服务配置
 | 
	
		
			
				|  |  | +    private $thirdPartyApiConfig = [
 | 
	
		
			
				|  |  | +        'url' => 'http://85cg744gf528.vicp.fun:25085/get_bodysize',
 | 
	
		
			
				|  |  | +        'timeout' => 120,
 | 
	
		
			
				|  |  | +        'connect_timeout' => 30
 | 
	
		
			
				|  |  | +    ];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * 开始AI身体测量分析
 | 
	
	
		
			
				|  | @@ -267,6 +274,486 @@ class AiMeasurement extends Api
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  | +     * 直接调用第三方AI测量服务
 | 
	
		
			
				|  |  | +     * @ApiMethod (POST)
 | 
	
		
			
				|  |  | +     * @ApiParams (name="profile_id", type="integer", required=true, description="档案ID")
 | 
	
		
			
				|  |  | +     * @ApiParams (name="photos", type="object", required=true, description="身体照片对象")
 | 
	
		
			
				|  |  | +     * @ApiParams (name="photos.front", type="string", required=true, description="正面照片URL")
 | 
	
		
			
				|  |  | +     * @ApiParams (name="photos.side", type="string", required=true, description="侧面照片URL") 
 | 
	
		
			
				|  |  | +     * @ApiParams (name="photos.back", type="string", required=true, description="背面照片URL")
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public function measurementDirect()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        $params = $this->request->post();
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        // 验证必要参数
 | 
	
		
			
				|  |  | +        if (empty($params['profile_id'])) {
 | 
	
		
			
				|  |  | +            $this->error('档案ID不能为空');
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (empty($params['photos']) || !is_array($params['photos'])) {
 | 
	
		
			
				|  |  | +            $this->error('请上传身体照片');
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // try {
 | 
	
		
			
				|  |  | +            // 验证档案归属
 | 
	
		
			
				|  |  | +            $profile = \app\common\model\BodyProfile::where('id', $params['profile_id'])
 | 
	
		
			
				|  |  | +                ->where('user_id', $this->auth->id)
 | 
	
		
			
				|  |  | +                ->find();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (!$profile) {
 | 
	
		
			
				|  |  | +                $this->error('档案不存在');
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // 验证照片格式
 | 
	
		
			
				|  |  | +            $requiredPhotos = ['front', 'side', 'back'];
 | 
	
		
			
				|  |  | +            foreach ($requiredPhotos as $angle) {
 | 
	
		
			
				|  |  | +                if (empty($params['photos'][$angle])) {
 | 
	
		
			
				|  |  | +                    $this->error("请上传{$angle}角度的身体照片");
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // 安全调用第三方AI服务 - 确保身高为数字格式
 | 
	
		
			
				|  |  | +            $heightCm = is_numeric($profile->height) ? floatval($profile->height) : 0;
 | 
	
		
			
				|  |  | +            $measurements = $this->safeCallThirdPartyAiService(
 | 
	
		
			
				|  |  | +                $params['photos'],
 | 
	
		
			
				|  |  | +                $heightCm
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            // echo "<pre>";
 | 
	
		
			
				|  |  | +            // print_r($measurements);
 | 
	
		
			
				|  |  | +            // echo "</pre>";
 | 
	
		
			
				|  |  | +            // exit;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // 处理结果
 | 
	
		
			
				|  |  | +            // $result = [
 | 
	
		
			
				|  |  | +            //     'measurements' => $measurements,
 | 
	
		
			
				|  |  | +            //     'confidence' => $measurements['_confidence'] ?? 0.8,
 | 
	
		
			
				|  |  | +            //     'warnings' => $measurements['_warnings'] ?? []
 | 
	
		
			
				|  |  | +            // ];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // 清理内部字段
 | 
	
		
			
				|  |  | +            // unset($result['measurements']['_confidence']);
 | 
	
		
			
				|  |  | +            // unset($result['measurements']['_warnings']);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            // 格式化结果用于展示
 | 
	
		
			
				|  |  | +            //$formattedResult = $this->formatMeasurementResult($result, $params['profile_id']);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            $this->success('AI测量完成', $measurements);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // } catch (\Exception $e) {
 | 
	
		
			
				|  |  | +        //     $this->error($e->getMessage());
 | 
	
		
			
				|  |  | +        // }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 调用第三方AI测量接口
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private function callThirdPartyAiService($photos, $height)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // 第三方API配置
 | 
	
		
			
				|  |  | +        $apiUrl = $this->thirdPartyApiConfig['url'];
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            // 准备请求数据 - 确保身高为纯数字(厘米)
 | 
	
		
			
				|  |  | +            $heightValue = is_numeric($height) ? floatval($height) : 0;
 | 
	
		
			
				|  |  | +            $requestData = [
 | 
	
		
			
				|  |  | +                'height' => $heightValue
 | 
	
		
			
				|  |  | +            ];
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 处理照片数据 - 转换为base64格式
 | 
	
		
			
				|  |  | +            if (isset($photos['front'])) {
 | 
	
		
			
				|  |  | +                $requestData['image1'] = $this->convertImageToBase64($photos['front']);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (isset($photos['side'])) {
 | 
	
		
			
				|  |  | +                $requestData['image2'] = $this->convertImageToBase64($photos['side']);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (isset($photos['back'])) {
 | 
	
		
			
				|  |  | +                $requestData['image3'] = $this->convertImageToBase64($photos['back']);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // 记录请求日志(不包含图片数据)
 | 
	
		
			
				|  |  | +            // $logData = [
 | 
	
		
			
				|  |  | +            //     'url' => $apiUrl,
 | 
	
		
			
				|  |  | +            //     'height' => $requestData['height'],
 | 
	
		
			
				|  |  | +            //     'image_count' => count(array_filter([
 | 
	
		
			
				|  |  | +            //         isset($requestData['image1']),
 | 
	
		
			
				|  |  | +            //         isset($requestData['image2']),
 | 
	
		
			
				|  |  | +            //         isset($requestData['image3'])
 | 
	
		
			
				|  |  | +            //     ]))
 | 
	
		
			
				|  |  | +            // ];
 | 
	
		
			
				|  |  | +            // 记录请求日志(包含身高和图片base64数据的前50个字符)
 | 
	
		
			
				|  |  | +            $logData = [
 | 
	
		
			
				|  |  | +                'url' => $apiUrl,
 | 
	
		
			
				|  |  | +                'height' => $heightValue . 'cm',
 | 
	
		
			
				|  |  | +                'image1_preview' => isset($requestData['image1']) ? substr($requestData['image1'], 0, 50) . '...' : null,
 | 
	
		
			
				|  |  | +                'image2_preview' => isset($requestData['image2']) ? substr($requestData['image2'], 0, 50) . '...' : null,
 | 
	
		
			
				|  |  | +                'image3_preview' => isset($requestData['image3']) ? substr($requestData['image3'], 0, 50) . '...' : null,
 | 
	
		
			
				|  |  | +                'request_data_size' => strlen(json_encode($requestData)) . ' bytes'
 | 
	
		
			
				|  |  | +            ];
 | 
	
		
			
				|  |  | +            \think\Log::info('Calling third party AI service: ' . json_encode($logData));
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 发送POST请求
 | 
	
		
			
				|  |  | +            $ch = curl_init();
 | 
	
		
			
				|  |  | +            curl_setopt_array($ch, [
 | 
	
		
			
				|  |  | +                CURLOPT_URL => $apiUrl,
 | 
	
		
			
				|  |  | +                CURLOPT_POST => true,
 | 
	
		
			
				|  |  | +                CURLOPT_POSTFIELDS => json_encode($requestData),
 | 
	
		
			
				|  |  | +                CURLOPT_RETURNTRANSFER => true,
 | 
	
		
			
				|  |  | +                CURLOPT_TIMEOUT => $this->thirdPartyApiConfig['timeout'],
 | 
	
		
			
				|  |  | +                CURLOPT_CONNECTTIMEOUT => $this->thirdPartyApiConfig['connect_timeout'],
 | 
	
		
			
				|  |  | +                CURLOPT_HTTPHEADER => [
 | 
	
		
			
				|  |  | +                    'Content-Type: application/json',
 | 
	
		
			
				|  |  | +                    'Accept: application/json'
 | 
	
		
			
				|  |  | +                ],
 | 
	
		
			
				|  |  | +                CURLOPT_SSL_VERIFYPEER => false,
 | 
	
		
			
				|  |  | +                CURLOPT_SSL_VERIFYHOST => false
 | 
	
		
			
				|  |  | +            ]);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            $response = curl_exec($ch);
 | 
	
		
			
				|  |  | +            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
	
		
			
				|  |  | +            $error = curl_error($ch);
 | 
	
		
			
				|  |  | +            curl_close($ch);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if ($error) {
 | 
	
		
			
				|  |  | +                throw new \Exception('请求第三方AI服务失败: ' . $error);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 处理各种HTTP错误状态
 | 
	
		
			
				|  |  | +            if ($httpCode >= 500) {
 | 
	
		
			
				|  |  | +                throw new \Exception('第三方AI服务内部错误: HTTP ' . $httpCode);
 | 
	
		
			
				|  |  | +            } elseif ($httpCode >= 400) {
 | 
	
		
			
				|  |  | +                throw new \Exception('第三方AI服务请求错误: HTTP ' . $httpCode);
 | 
	
		
			
				|  |  | +            } elseif ($httpCode !== 200) {
 | 
	
		
			
				|  |  | +                throw new \Exception('第三方AI服务返回异常状态: HTTP ' . $httpCode);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 检查响应内容
 | 
	
		
			
				|  |  | +            if (empty($response)) {
 | 
	
		
			
				|  |  | +                throw new \Exception('第三方AI服务返回空响应');
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            $result = json_decode($response, true);
 | 
	
		
			
				|  |  | +            if (json_last_error() !== JSON_ERROR_NONE) {
 | 
	
		
			
				|  |  | +                throw new \Exception('第三方AI服务返回数据格式错误');
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 记录API响应信息
 | 
	
		
			
				|  |  | +            $responseLog = [
 | 
	
		
			
				|  |  | +                'http_code' => $httpCode,
 | 
	
		
			
				|  |  | +                'response_size' => strlen($response) . ' bytes',
 | 
	
		
			
				|  |  | +                'has_body_size' => isset($result['body_size']),
 | 
	
		
			
				|  |  | +                'body_size_fields' => isset($result['body_size']) ? array_keys($result['body_size']) : [],
 | 
	
		
			
				|  |  | +                'response_preview' => substr($response, 0, 200) . '...'
 | 
	
		
			
				|  |  | +            ];
 | 
	
		
			
				|  |  | +            \think\Log::info('Third party AI service response: ' . json_encode($responseLog));
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 处理返回的测量数据
 | 
	
		
			
				|  |  | +            // echo "<pre>";
 | 
	
		
			
				|  |  | +            // print_r($result);
 | 
	
		
			
				|  |  | +            // echo "</pre>";
 | 
	
		
			
				|  |  | +            // exit;
 | 
	
		
			
				|  |  | +            return $this->processMeasurementData($result);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +        } catch (\Exception $e) {
 | 
	
		
			
				|  |  | +            // 记录错误日志
 | 
	
		
			
				|  |  | +            \think\Log::error('Third party AI service error: ' . $e->getMessage());
 | 
	
		
			
				|  |  | +            throw $e;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        // 如果执行到这里说明没有异常处理,直接返回处理结果
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 安全调用第三方AI服务(带异常处理和默认返回)
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private function safeCallThirdPartyAiService($photos, $height)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            return $this->callThirdPartyAiService($photos, $height);
 | 
	
		
			
				|  |  | +        } catch (\Exception $e) {
 | 
	
		
			
				|  |  | +            // 记录错误日志
 | 
	
		
			
				|  |  | +            \think\Log::error('Third party AI service error, returning default data: ' . $e->getMessage());
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 返回默认的空测量数据
 | 
	
		
			
				|  |  | +            return $this->getDefaultMeasurementData();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 获取默认的空测量数据
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private function getDefaultMeasurementData()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // 返回所有映射字段的空值(使用空字符串)
 | 
	
		
			
				|  |  | +        return [
 | 
	
		
			
				|  |  | +            'waist' => '',            // 腰围
 | 
	
		
			
				|  |  | +            'thigh' => '',            // 大腿围
 | 
	
		
			
				|  |  | +            'neck' => '',             // 颈围
 | 
	
		
			
				|  |  | +            'knee' => '',             // 膝围
 | 
	
		
			
				|  |  | +            'chest' => '',            // 胸围
 | 
	
		
			
				|  |  | +            'calf' => '',             // 小腿围
 | 
	
		
			
				|  |  | +            'leg_length' => '',       // 腿长
 | 
	
		
			
				|  |  | +            'hip' => '',              // 臀围
 | 
	
		
			
				|  |  | +            'inner_leg' => '',        // 内腿长
 | 
	
		
			
				|  |  | +            'hip_actual' => '',       // 实际臀围
 | 
	
		
			
				|  |  | +            'waist_lower' => '',      // 下腰围
 | 
	
		
			
				|  |  | +            'shoulder_width' => '',   // 肩宽
 | 
	
		
			
				|  |  | +            'arm_length' => '',       // 臂长
 | 
	
		
			
				|  |  | +            'wrist' => '',            // 手腕围
 | 
	
		
			
				|  |  | +            'upper_arm' => '',        // 上臂围
 | 
	
		
			
				|  |  | +            'mid_waist' => '',        // 中腰围
 | 
	
		
			
				|  |  | +            'ankle' => '',            // 脚踝围
 | 
	
		
			
				|  |  | +            '_confidence' => 0.0,
 | 
	
		
			
				|  |  | +            '_warnings' => ['第三方AI服务暂时不可用,返回默认数据']
 | 
	
		
			
				|  |  | +        ];
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 测试接口 - 返回模拟的第三方API测量数据
 | 
	
		
			
				|  |  | +     * @ApiMethod (POST)
 | 
	
		
			
				|  |  | +     * @ApiParams (name="profile_id", type="integer", required=true, description="档案ID")
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public function testMeasurementData()
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        $params = $this->request->post();
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        // 验证必要参数
 | 
	
		
			
				|  |  | +        if (empty($params['profile_id'])) {
 | 
	
		
			
				|  |  | +            $this->error('档案ID不能为空');
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 验证档案归属
 | 
	
		
			
				|  |  | +        $profile = \app\common\model\BodyProfile::where('id', $params['profile_id'])
 | 
	
		
			
				|  |  | +            ->where('user_id', $this->auth->id)
 | 
	
		
			
				|  |  | +            ->find();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (!$profile) {
 | 
	
		
			
				|  |  | +            $this->error('档案不存在');
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 模拟第三方API返回的数据
 | 
	
		
			
				|  |  | +        $mockApiResult = [
 | 
	
		
			
				|  |  | +            "body_size" => [
 | 
	
		
			
				|  |  | +                "datuigen" => 56.973762220946064,
 | 
	
		
			
				|  |  | +                "duwei" => 71.86294164495045,
 | 
	
		
			
				|  |  | +                "jiankuan" => 44.99356951672863,
 | 
	
		
			
				|  |  | +                "jiaohuai" => 20.995062499529606,
 | 
	
		
			
				|  |  | +                "jingwei" => 36.973537078225604,
 | 
	
		
			
				|  |  | +                "neitui" => 67.99506048261769,
 | 
	
		
			
				|  |  | +                "shangbi" => 23.285375591374667,
 | 
	
		
			
				|  |  | +                "shoubichang" => 61.1834335984307,
 | 
	
		
			
				|  |  | +                "shouwanwei" => 16.0697059847192,
 | 
	
		
			
				|  |  | +                "tuichang" => 73.9800462219755,
 | 
	
		
			
				|  |  | +                "tunwei" => 90.08082593388505,
 | 
	
		
			
				|  |  | +                "xiaofu" => 70.98010845587423,
 | 
	
		
			
				|  |  | +                "xiaotuiwei" => 37.2761443409742,
 | 
	
		
			
				|  |  | +                "xigai" => 34.990971006868364,
 | 
	
		
			
				|  |  | +                "xiongwei" => 81.85738385794711,
 | 
	
		
			
				|  |  | +                "yaowei" => 72.93800974219818,
 | 
	
		
			
				|  |  | +                "zhongyao" => 70.99945416888724
 | 
	
		
			
				|  |  | +            ],
 | 
	
		
			
				|  |  | +            "confidence" => 0.85
 | 
	
		
			
				|  |  | +        ];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 处理测量数据
 | 
	
		
			
				|  |  | +        $measurements = $this->processMeasurementData($mockApiResult);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 处理结果
 | 
	
		
			
				|  |  | +        $result = [
 | 
	
		
			
				|  |  | +            'measurements' => $measurements,
 | 
	
		
			
				|  |  | +            'confidence' => $measurements['_confidence'] ?? 0.8,
 | 
	
		
			
				|  |  | +            'warnings' => $measurements['_warnings'] ?? []
 | 
	
		
			
				|  |  | +        ];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 清理内部字段
 | 
	
		
			
				|  |  | +        unset($result['measurements']['_confidence']);
 | 
	
		
			
				|  |  | +        unset($result['measurements']['_warnings']);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 格式化结果用于展示
 | 
	
		
			
				|  |  | +        $formattedResult = $this->formatMeasurementResult($result, $params['profile_id']);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        $this->success('测试数据返回成功', [
 | 
	
		
			
				|  |  | +            'original_api_data' => $mockApiResult['body_size'],
 | 
	
		
			
				|  |  | +            'mapped_measurements' => $result['measurements'],
 | 
	
		
			
				|  |  | +            'formatted_result' => $formattedResult
 | 
	
		
			
				|  |  | +        ]);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 将图片URL转换为base64格式
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private function convertImageToBase64($imageUrl)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            // 如果已经是base64格式,直接返回
 | 
	
		
			
				|  |  | +            if (strpos($imageUrl, 'data:image') === 0) {
 | 
	
		
			
				|  |  | +                return $imageUrl;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 如果是相对路径,转换为绝对路径
 | 
	
		
			
				|  |  | +            if (strpos($imageUrl, 'http') !== 0) {
 | 
	
		
			
				|  |  | +                $imageUrl = request()->domain() . $imageUrl;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 获取图片数据
 | 
	
		
			
				|  |  | +            $ch = curl_init();
 | 
	
		
			
				|  |  | +            curl_setopt_array($ch, [
 | 
	
		
			
				|  |  | +                CURLOPT_URL => $imageUrl,
 | 
	
		
			
				|  |  | +                CURLOPT_RETURNTRANSFER => true,
 | 
	
		
			
				|  |  | +                CURLOPT_TIMEOUT => 30,
 | 
	
		
			
				|  |  | +                CURLOPT_CONNECTTIMEOUT => 10,
 | 
	
		
			
				|  |  | +                CURLOPT_FOLLOWLOCATION => true,
 | 
	
		
			
				|  |  | +                CURLOPT_SSL_VERIFYPEER => false,
 | 
	
		
			
				|  |  | +                CURLOPT_SSL_VERIFYHOST => false,
 | 
	
		
			
				|  |  | +                CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
 | 
	
		
			
				|  |  | +                CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
 | 
	
		
			
				|  |  | +                CURLOPT_HTTPHEADER => [
 | 
	
		
			
				|  |  | +                    'Accept: image/webp,image/apng,image/*,*/*;q=0.8',
 | 
	
		
			
				|  |  | +                    'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
 | 
	
		
			
				|  |  | +                    'Cache-Control: no-cache',
 | 
	
		
			
				|  |  | +                ]
 | 
	
		
			
				|  |  | +            ]);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            $imageData = curl_exec($ch);
 | 
	
		
			
				|  |  | +            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
 | 
	
		
			
				|  |  | +            $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
 | 
	
		
			
				|  |  | +            $curlError = curl_error($ch);
 | 
	
		
			
				|  |  | +            $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
 | 
	
		
			
				|  |  | +            curl_close($ch);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 详细的错误处理
 | 
	
		
			
				|  |  | +            if ($curlError) {
 | 
	
		
			
				|  |  | +                throw new \Exception("网络请求失败: {$curlError} (URL: {$imageUrl})");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if ($httpCode !== 200) {
 | 
	
		
			
				|  |  | +                throw new \Exception("图片访问失败,HTTP状态码: {$httpCode} (URL: {$imageUrl})");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if (!$imageData || strlen($imageData) === 0) {
 | 
	
		
			
				|  |  | +                throw new \Exception("图片数据为空 (URL: {$imageUrl})");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 验证图片数据是否有效
 | 
	
		
			
				|  |  | +            if (!@getimagesizefromstring($imageData)) {
 | 
	
		
			
				|  |  | +                throw new \Exception("获取的数据不是有效的图片格式 (URL: {$imageUrl})");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 确定MIME类型
 | 
	
		
			
				|  |  | +            if (strpos($contentType, 'image/') === 0) {
 | 
	
		
			
				|  |  | +                $mimeType = $contentType;
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                // 通过文件扩展名推断
 | 
	
		
			
				|  |  | +                $extension = strtolower(pathinfo(parse_url($imageUrl, PHP_URL_PATH), PATHINFO_EXTENSION));
 | 
	
		
			
				|  |  | +                $mimeTypes = [
 | 
	
		
			
				|  |  | +                    'jpg' => 'image/jpeg',
 | 
	
		
			
				|  |  | +                    'jpeg' => 'image/jpeg',
 | 
	
		
			
				|  |  | +                    'png' => 'image/png',
 | 
	
		
			
				|  |  | +                    'gif' => 'image/gif',
 | 
	
		
			
				|  |  | +                    'webp' => 'image/webp',
 | 
	
		
			
				|  |  | +                    'bmp' => 'image/bmp'
 | 
	
		
			
				|  |  | +                ];
 | 
	
		
			
				|  |  | +                $mimeType = $mimeTypes[$extension] ?? 'image/jpeg';
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 转换为base64
 | 
	
		
			
				|  |  | +            $base64 = base64_encode($imageData);
 | 
	
		
			
				|  |  | +            return "data:{$mimeType};base64,{$base64}";
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +        } catch (\Exception $e) {
 | 
	
		
			
				|  |  | +            \think\Log::error('Convert image to base64 error: ' . $e->getMessage() . ' URL: ' . $imageUrl);
 | 
	
		
			
				|  |  | +            throw new \Exception('图片转换失败: ' . $e->getMessage());
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * 处理第三方API返回的测量数据
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    private function processMeasurementData($apiResult)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        // 根据第三方API的返回格式处理数据
 | 
	
		
			
				|  |  | +        // 这里需要根据实际的API返回格式进行调整
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        $measurements = [];
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            // 假设API返回格式类似:
 | 
	
		
			
				|  |  | +            // {
 | 
	
		
			
				|  |  | +            //   "status": "success",
 | 
	
		
			
				|  |  | +            //   "data": {
 | 
	
		
			
				|  |  | +            //     "chest": 95.5,
 | 
	
		
			
				|  |  | +            //     "waist": 75.2,
 | 
	
		
			
				|  |  | +            //     "hip": 98.7,
 | 
	
		
			
				|  |  | +            //     ...
 | 
	
		
			
				|  |  | +            //   },
 | 
	
		
			
				|  |  | +            //   "confidence": 0.85
 | 
	
		
			
				|  |  | +            // }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            // 检查返回数据结构 - 可能直接包含body_size字段
 | 
	
		
			
				|  |  | +            if (isset($apiResult['body_size'])) {
 | 
	
		
			
				|  |  | +                $data = $apiResult['body_size'];
 | 
	
		
			
				|  |  | +                $hasValidData = true;
 | 
	
		
			
				|  |  | +            } elseif (isset($apiResult['status']) && $apiResult['status'] === 'success') {
 | 
	
		
			
				|  |  | +                $data = $apiResult['data'] ?? [];
 | 
	
		
			
				|  |  | +                $hasValidData = true;
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                // 尝试直接使用返回的数据
 | 
	
		
			
				|  |  | +                $data = $apiResult;
 | 
	
		
			
				|  |  | +                $hasValidData = !empty($data) && is_array($data);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if ($hasValidData) {
 | 
	
		
			
				|  |  | +                // 映射字段名(根据第三方API返回的字段名进行映射)
 | 
	
		
			
				|  |  | +                $fieldMapping = [
 | 
	
		
			
				|  |  | +                    'yaowei' => 'waist',          // 净腰围 → 腰围
 | 
	
		
			
				|  |  | +                    'datuigen' => 'thigh',        // 净腿根 → 大腿围
 | 
	
		
			
				|  |  | +                    'jingwei' => 'neck',          // 净颈围 → 颈围
 | 
	
		
			
				|  |  | +                    'xigai' => 'knee',            // 净膝围 → 膝围
 | 
	
		
			
				|  |  | +                    'xiongwei' => 'chest',        // 净胸围 → 胸围
 | 
	
		
			
				|  |  | +                    'xiaotuiwei' => 'calf',       // 净小腿围 → 小腿围
 | 
	
		
			
				|  |  | +                    'tuichang' => 'leg_length',   // 腿长 → 腿长
 | 
	
		
			
				|  |  | +                    'duwei' => 'hip',             // 净肚围 → 臀围
 | 
	
		
			
				|  |  | +                    'neitui' => 'inner_leg',      // 内腿长 → 内腿长
 | 
	
		
			
				|  |  | +                    'tunwei' => 'hip_actual',     // 净臀围 → 实际臀围
 | 
	
		
			
				|  |  | +                    'xiaofu' => 'waist_lower',    // 净小腹围 → 下腰围
 | 
	
		
			
				|  |  | +                    'jiankuan' => 'shoulder_width', // 净肩宽 → 肩宽
 | 
	
		
			
				|  |  | +                    'shoubichang' => 'arm_length', // 净手臂长 → 臂长
 | 
	
		
			
				|  |  | +                    'shouwanwei' => 'wrist',      // 净手腕围 → 手腕围
 | 
	
		
			
				|  |  | +                    'shangbi' => 'upper_arm',     // 净上臂围 → 上臂围
 | 
	
		
			
				|  |  | +                    'zhongyao' => 'mid_waist',    // 净中腰 → 中腰围
 | 
	
		
			
				|  |  | +                    'jiaohuai' => 'ankle',        // 净脚踝围 → 脚踝围
 | 
	
		
			
				|  |  | +                ];
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                foreach ($fieldMapping as $apiField => $localField) {
 | 
	
		
			
				|  |  | +                    if (isset($data[$apiField]) && is_numeric($data[$apiField])) {
 | 
	
		
			
				|  |  | +                        $measurements[$localField] = round(floatval($data[$apiField]), 1);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                // 设置置信度和警告信息
 | 
	
		
			
				|  |  | +                $measurements['_confidence'] = $apiResult['confidence'] ?? 0.8;
 | 
	
		
			
				|  |  | +                $measurements['_warnings'] = $apiResult['warnings'] ?? [];
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                // 如果没有测量数据,添加默认警告
 | 
	
		
			
				|  |  | +                if (count($measurements) === 2) { // 只有_confidence和_warnings
 | 
	
		
			
				|  |  | +                    $measurements['_warnings'][] = '第三方AI服务未返回有效的测量数据';
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                // API返回错误
 | 
	
		
			
				|  |  | +                $errorMsg = $apiResult['message'] ?? $apiResult['error'] ?? '第三方AI服务返回未知错误';
 | 
	
		
			
				|  |  | +                throw new \Exception($errorMsg);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +        } catch (\Exception $e) {
 | 
	
		
			
				|  |  | +            // 处理异常,返回错误信息
 | 
	
		
			
				|  |  | +            $measurements['_confidence'] = 0;
 | 
	
		
			
				|  |  | +            $measurements['_warnings'] = ['数据处理失败: ' . $e->getMessage()];
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        return $measurements;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  |       * 处理AI测量任务
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      private function processTask($taskId)
 | 
	
	
		
			
				|  | @@ -287,12 +774,11 @@ class AiMeasurement extends Api
 | 
	
		
			
				|  |  |              $photos = json_decode($task['photos'], true);
 | 
	
		
			
				|  |  |              $params = json_decode($task['params'], true);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            // 调用AI分析服务
 | 
	
		
			
				|  |  | -            $measurements = AiMeasurementService::analyzBodyPhotos(
 | 
	
		
			
				|  |  | +            // 安全调用第三方AI分析服务 - 确保身高为数字格式
 | 
	
		
			
				|  |  | +            $heightCm = is_numeric($params['height']) ? floatval($params['height']) : 0;
 | 
	
		
			
				|  |  | +            $measurements = $this->safeCallThirdPartyAiService(
 | 
	
		
			
				|  |  |                  $photos,
 | 
	
		
			
				|  |  | -                $params['gender'],
 | 
	
		
			
				|  |  | -                $params['height'],
 | 
	
		
			
				|  |  | -                $params['weight']
 | 
	
		
			
				|  |  | +                $heightCm
 | 
	
		
			
				|  |  |              );
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // 处理结果
 |