| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151 | 
							- <?php
 
- namespace app\api\controller;
 
- use app\common\controller\Api;
 
- use app\common\service\AiMeasurementService;
 
- use app\common\service\BodyProfileService;
 
- use app\api\validate\BodyProfile as BodyProfileValidate;
 
- use think\Cache;
 
- /**
 
-  * AI测量API控制器
 
-  */
 
- 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身体测量分析
 
-      */
 
-     public function startAnalysis()
 
-     {
 
-         $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('档案不存在');
 
-             }
 
-             // 检查是否有正在处理的任务
 
-             $existingTask = \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('profile_id', $params['profile_id'])
 
-                 ->where('status', 'in', [0, 1]) // 待处理或处理中
 
-                 ->find();
 
-             if ($existingTask) {
 
-                 $this->error('该档案已有正在处理的AI测量任务,请稍后再试');
 
-             }
 
-             // 验证照片格式
 
-             $requiredPhotos = ['front', 'side', 'back'];
 
-             foreach ($requiredPhotos as $angle) {
 
-                 if (empty($params['photos'][$angle])) {
 
-                     $this->error("请上传{$angle}角度的身体照片");
 
-                 }
 
-             }
 
-             // 创建AI测量任务
 
-             $taskData = [
 
-                 'profile_id' => $params['profile_id'],
 
-                 'user_id' => $this->auth->id,
 
-                 'photos' => json_encode($params['photos']),
 
-                 'params' => json_encode([
 
-                     'gender' => $profile->gender,
 
-                     'height' => $profile->height,
 
-                     'weight' => $profile->weight
 
-                 ]),
 
-                 'priority' => $params['priority'] ?? 5,
 
-                 'status' => 0, // 待处理
 
-                 'createtime' => time(),
 
-                 'updatetime' => time()
 
-             ];
 
-             $taskId = \think\Db::table('fa_ai_measurement_task')->insertGetId($taskData);
 
-             // 立即处理任务(也可以放到队列中异步处理)
 
-             $this->processTask($taskId);
 
-             $this->success('AI测量分析已开始', [
 
-                 'task_id' => $taskId,
 
-                 'estimated_time' => 30 // 预计处理时间(秒)
 
-             ]);
 
-         } catch (\Exception $e) {
 
-             $this->error($e->getMessage());
 
-         }
 
-     }
 
-     /**
 
-      * 获取AI测量结果
 
-      */
 
-     public function getResult()
 
-     {
 
-         $taskId = $this->request->get('task_id/d');
 
-         $profileId = $this->request->get('profile_id/d');
 
-         if (!$taskId && !$profileId) {
 
-             $this->error('任务ID或档案ID不能为空');
 
-         }
 
-         try {
 
-             $query = \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('user_id', $this->auth->id);
 
-             if ($taskId) {
 
-                 $query->where('id', $taskId);
 
-             } else {
 
-                 $query->where('profile_id', $profileId)->order('id DESC');
 
-             }
 
-             $task = $query->find();
 
-             if (!$task) {
 
-                 $this->error('任务不存在');
 
-             }
 
-             // 根据任务状态返回不同结果
 
-             switch ($task['status']) {
 
-                 case 0: // 待处理
 
-                     $this->success('任务排队中', [
 
-                         'status' => 'pending',
 
-                         'message' => '任务正在排队等待处理'
 
-                     ]);
 
-                     break;
 
-                 case 1: // 处理中
 
-                     $this->success('正在分析中', [
 
-                         'status' => 'processing',
 
-                         'message' => 'AI正在分析您的身体照片,请稍候...',
 
-                         'progress' => $this->estimateProgress($task)
 
-                     ]);
 
-                     break;
 
-                 case 2: // 完成
 
-                     $result = json_decode($task['result'], true);
 
-                     $this->success('分析完成', [
 
-                         'status' => 'completed',
 
-                         'data' => $this->formatMeasurementResult($result, $task['profile_id'])
 
-                     ]);
 
-                     break;
 
-                 case 3: // 失败
 
-                     $this->success('分析失败', [
 
-                         'status' => 'failed',
 
-                         'message' => $task['error_message'] ?: '分析过程中出现错误',
 
-                         'can_retry' => $task['attempts'] < $task['max_attempts']
 
-                     ]);
 
-                     break;
 
-                 default:
 
-                     $this->error('未知的任务状态');
 
-             }
 
-         } catch (\Exception $e) {
 
-             $this->error($e->getMessage());
 
-         }
 
-     }
 
-     /**
 
-      * 保存AI测量结果
 
-      */
 
-     public function saveResult()
 
-     {
 
-         $params = $this->request->post();
 
-         
 
-         if (empty($params['task_id'])) {
 
-             $this->error('任务ID不能为空');
 
-         }
 
-         try {
 
-             // 获取任务信息
 
-             $task = \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('id', $params['task_id'])
 
-                 ->where('user_id', $this->auth->id)
 
-                 ->where('status', 2) // 只有完成的任务才能保存
 
-                 ->find();
 
-             if (!$task) {
 
-                 $this->error('任务不存在或未完成');
 
-             }
 
-             $result = json_decode($task['result'], true);
 
-             if (!$result || !isset($result['measurements'])) {
 
-                 $this->error('测量结果数据异常');
 
-             }
 
-             // 保存测量数据
 
-             $measurement = AiMeasurementService::saveMeasurementResult(
 
-                 $task['profile_id'],
 
-                 $result['measurements'],
 
-                 json_decode($task['photos'], true),
 
-                 $result['confidence'] ?? null
 
-             );
 
-             $this->success('测量结果已保存', [
 
-                 'measurement_id' => $measurement->id
 
-             ]);
 
-         } catch (\Exception $e) {
 
-             $this->error($e->getMessage());
 
-         }
 
-     }
 
-     /**
 
-      * 重新分析
 
-      */
 
-     public function retryAnalysis()
 
-     {
 
-         $taskId = $this->request->post('task_id/d');
 
-         if (!$taskId) {
 
-             $this->error('任务ID不能为空');
 
-         }
 
-         try {
 
-             $task = \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('id', $taskId)
 
-                 ->where('user_id', $this->auth->id)
 
-                 ->where('status', 3) // 失败的任务
 
-                 ->find();
 
-             if (!$task) {
 
-                 $this->error('任务不存在或不允许重试');
 
-             }
 
-             if ($task['attempts'] >= $task['max_attempts']) {
 
-                 $this->error('重试次数已达上限');
 
-             }
 
-             // 重置任务状态
 
-             \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('id', $taskId)
 
-                 ->update([
 
-                     'status' => 0,
 
-                     'error_message' => '',
 
-                     'updatetime' => time()
 
-                 ]);
 
-             // 重新处理任务
 
-             $this->processTask($taskId);
 
-             $this->success('已重新开始分析');
 
-         } catch (\Exception $e) {
 
-             $this->error($e->getMessage());
 
-         }
 
-     }
 
-     /**
 
-      * 获取测量字段配置
 
-      */
 
-     public function getMeasurementConfig()
 
-     {
 
-         $gender = $this->request->get('gender/d', 1);
 
-         try {
 
-             $config = AiMeasurementService::getMeasurementDisplayConfig($gender);
 
-             $this->success('获取成功', $config);
 
-         } catch (\Exception $e) {
 
-             $this->error($e->getMessage());
 
-         }
 
-     }
 
-     /**
 
-      * 获取AI测量素材配置
 
-      * @ApiMethod (GET)
 
-      * @ApiParams (name="mode", type="string", required=false, description="模式:selfie(自拍)、helper(帮拍)、common(通用)、all(全部)")
 
-      */
 
-     public function getMaterialConfig()
 
-     {
 
-         $mode = $this->request->get('mode', 'all');
 
-         // try {
 
-             $config = [];
 
-             
 
-             switch ($mode) {
 
-                 case 'selfie':
 
-                     $config = $this->getSelfieConfig();
 
-                     break;
 
-                     
 
-                 case 'helper':
 
-                     $config = $this->getHelperConfig();
 
-                     break;
 
-                     
 
-                 case 'common':
 
-                     $config = $this->getCommonConfig();
 
-                     break;
 
-                     
 
-                 case 'all':
 
-                 default:
 
-                     $config = [
 
-                         'selfie' => $this->getSelfieConfig(),
 
-                         'helper' => $this->getHelperConfig(),
 
-                         'common' => $this->getCommonConfig()
 
-                     ];
 
-                     break;
 
-             }
 
-             $this->success('获取成功', $config);
 
-         // } catch (\Exception $e) {
 
-         //     $this->error($e->getMessage());
 
-         // }
 
-     }
 
-     /**
 
-      * 获取自拍模式配置
 
-      */
 
-     private function getSelfieConfig()
 
-     {
 
-         return [
 
-             'enabled' => config('site.ai_measure_selfie_enabled'),
 
-             'intro_image' => $this->formatFileUrl(config('site.ai_measure_selfie_intro_image')),
 
-             // 引导教程
 
-             'tutorial' => [
 
-                 'images' => $this->parseImages(config('site.ai_measure_selfie_tutorial_images')),
 
-                 'video' => $this->formatFileUrl(config('site.ai_measure_selfie_tutorial_video'))
 
-             ],
 
-             
 
-             // 陀螺仪检测
 
-             'gyroscope' => [
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_selfie_gyro_voice')),
 
-                 'example' => $this->formatFileUrl(config('site.ai_measure_selfie_gyro_example'))
 
-             ],
 
-             
 
-             // 拍摄正面
 
-             'front_shooting' => [
 
-                 'frame' => $this->formatFileUrl(config('site.ai_measure_selfie_front_frame')),
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_selfie_front_voice')),
 
-                 'demo' => $this->formatFileUrl(config('site.ai_measure_selfie_front_demo'))
 
-             ],
 
-             
 
-             // 拍摄侧面
 
-             'side_shooting' => [
 
-                 'frame' => $this->formatFileUrl(config('site.ai_measure_selfie_side_frame')),
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_selfie_side_voice')),
 
-                 'demo' => $this->formatFileUrl(config('site.ai_measure_selfie_side_demo'))
 
-             ],
 
-             
 
-             // 拍摄正面侧平举
 
-             'arms_shooting' => [
 
-                 'frame' => $this->formatFileUrl(config('site.ai_measure_selfie_arms_frame')),
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_selfie_arms_voice')),
 
-                 'demo' => $this->formatFileUrl(config('site.ai_measure_selfie_arms_demo'))
 
-             ],
 
-             
 
-             // 拍摄过程素材
 
-             'process_materials' => [
 
-                 'countdown_voice' => $this->formatFileUrl(config('site.ai_measure_selfie_countdown_voice')),
 
-                 'timer_sound' => $this->formatFileUrl(config('site.ai_measure_selfie_timer_sound')),
 
-                 'complete_sound' => $this->formatFileUrl(config('site.ai_measure_selfie_complete_sound')),
 
-                 'next_voice' => $this->formatFileUrl(config('site.ai_measure_selfie_next_voice')),
 
-                 'finish_voice' => $this->formatFileUrl(config('site.ai_measure_selfie_finish_voice'))
 
-             ]
 
-         ];
 
-     }
 
-     /**
 
-      * 获取帮拍模式配置
 
-      */
 
-     private function getHelperConfig()
 
-     {
 
-         return [
 
-             'enabled' => config('site.ai_measure_helper_enabled'),
 
-             'intro_image' => $this->formatFileUrl(config('site.ai_measure_helper_intro_image')),
 
-             // 引导教程
 
-             'tutorial' => [
 
-                 'images' => $this->parseImages(config('site.ai_measure_helper_tutorial_images')),
 
-                 'video' => $this->formatFileUrl(config('site.ai_measure_helper_tutorial_video'))
 
-             ],
 
-             
 
-             // 陀螺仪检测
 
-             'gyroscope' => [
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_helper_gyro_voice')),
 
-                 'example' => $this->formatFileUrl(config('site.ai_measure_helper_gyro_example'))
 
-             ],
 
-             
 
-             // 拍摄正面
 
-             'front_shooting' => [
 
-                 'frame' => $this->formatFileUrl(config('site.ai_measure_helper_front_frame')),
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_helper_front_voice')),
 
-                 'demo' => $this->formatFileUrl(config('site.ai_measure_helper_front_demo'))
 
-             ],
 
-             
 
-             // 拍摄侧面
 
-             'side_shooting' => [
 
-                 'frame' => $this->formatFileUrl(config('site.ai_measure_helper_side_frame')),
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_helper_side_voice')),
 
-                 'demo' => $this->formatFileUrl(config('site.ai_measure_helper_side_demo'))
 
-             ],
 
-             
 
-             // 拍摄正面侧平举
 
-             'arms_shooting' => [
 
-                 'frame' => $this->formatFileUrl(config('site.ai_measure_helper_arms_frame')),
 
-                 'voice' => $this->formatFileUrl(config('site.ai_measure_helper_arms_voice')),
 
-                 'demo' => $this->formatFileUrl(config('site.ai_measure_helper_arms_demo'))
 
-             ],
 
-             
 
-             // 拍摄过程素材
 
-             'process_materials' => [
 
-                 'countdown_voice' => cdnurl(config('site.ai_measure_helper_countdown_voice')),
 
-                 'timer_sound' => $this->formatFileUrl(config('site.ai_measure_helper_timer_sound')),
 
-                 'complete_sound' => $this->formatFileUrl(config('site.ai_measure_helper_complete_sound')),
 
-                 'next_voice' => $this->formatFileUrl(config('site.ai_measure_helper_next_voice')),
 
-                 'finish_voice' => $this->formatFileUrl(config('site.ai_measure_helper_finish_voice'))
 
-             ]
 
-         ];
 
-     }
 
-     /**
 
-      * 获取通用配置
 
-      */
 
-     private function getCommonConfig()
 
-     {
 
-         return [
 
-             'privacy_notice' => config('site.ai_measure_privacy_notice'),
 
-             'accuracy_disclaimer' => config('site.ai_measure_accuracy_disclaimer'),
 
-             'measurement_types' => $this->parseMeasurementTypes(config('site.ai_measure_measurement_types')),
 
-             'ai_model_version' => config('site.ai_measure_ai_model_version'),
 
-             'feedback_enabled' => config('site.ai_measure_feedback_enabled'),
 
-             'debug_mode' => config('site.ai_measure_debug_mode'),
 
-             'log_level' => config('site.ai_measure_log_level')
 
-         ];
 
-     }
 
-     /**
 
-      * 格式化文件URL
 
-      */
 
-     private function formatFileUrl($url)
 
-     {
 
-         if (empty($url)) {
 
-             return null;
 
-         }
 
-         
 
-         return cdnurl($url);
 
-     }
 
-     /**
 
-      * 解析图片集合
 
-      */
 
-     private function parseImages($images)
 
-     {
 
-         if (empty($images)) {
 
-             return [];
 
-         }
 
-         
 
-         // 如果是JSON格式的字符串,解析为数组
 
-         if (is_string($images)) {
 
-             $imageArray = json_decode($images, true);
 
-             if (json_last_error() === JSON_ERROR_NONE && is_array($imageArray)) {
 
-                 $images = $imageArray;
 
-             } else {
 
-                 // 如果不是JSON,可能是逗号分隔的字符串
 
-                 $images = explode(',', $images);
 
-             }
 
-         }
 
-         
 
-         if (!is_array($images)) {
 
-             return [];
 
-         }
 
-         
 
-         // 格式化每个图片URL
 
-         return array_map(function($url) {
 
-             return $this->formatFileUrl(trim($url));
 
-         }, array_filter($images));
 
-     }
 
-     /**
 
-      * 解析测量类型
 
-      */
 
-     private function parseMeasurementTypes($types)
 
-     {
 
-         if (empty($types)) {
 
-             return [];
 
-         }
 
-         
 
-         $typeLabels = [
 
-             'bust' => '胸围',
 
-             'waist' => '腰围', 
 
-             'hips' => '臀围',
 
-             'shoulder' => '肩宽',
 
-             'sleeve' => '袖长',
 
-             'inseam' => '内缝长',
 
-             'outseam' => '外缝长',
 
-             'thigh' => '大腿围',
 
-             'neck' => '颈围'
 
-         ];
 
-         
 
-         $typeArray = is_string($types) ? explode(',', $types) : $types;
 
-         $result = [];
 
-         
 
-         foreach ($typeArray as $type) {
 
-             $type = trim($type);
 
-             if (isset($typeLabels[$type])) {
 
-                 $result[] = [
 
-                     'key' => $type,
 
-                     'label' => $typeLabels[$type]
 
-                 ];
 
-             }
 
-         }
 
-         
 
-         return $result;
 
-     }
 
-     /**
 
-      * 直接调用第三方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}角度的身体照片");
 
-             //     }
 
-             // }
 
-             // 直接使用档案的 
 
-             $photos = $profile->body_photos_text;
 
-             // 安全调用第三方AI服务 - 确保身高为数字格式
 
-             $heightCm = is_numeric($profile->height) ? floatval($profile->height) : 0;
 
-             $measurements = $this->safeCallThirdPartyAiService(
 
-                 $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']);
 
-             $measurements['height'] = $profile->height;
 
-             $measurements['weight'] = $profile->weight;
 
-             $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);
 
-             }
 
-             \think\Log::info('Third party AI service response: ' .$response);
 
-             // 检查响应内容
 
-             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) . '...'
 
-             // ];
 
-           
 
-             
 
-             // 处理返回的测量数据
 
-             // 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 [
 
-             'chest'=>'',        // 净胸围 → 胸围
 
-             'waist'=>'',          // 净腰围 → 腰围
 
-             'hip'=>'',           // 净臀围 → 实际臀围         
 
-             //'thigh',        // 净腿根 → 大腿围
 
-             'knee'=>'',            // 净膝围 → 膝围
 
-             'calf'=>'',       // 净小腿围 → 小腿围                  
 
-             'arm_length'=>'', // 净手臂长 → 臂长
 
-             'wrist'=>'',      // 净手腕围 → 手腕围                  
 
-             'pants_length'=>'',   // 腿长 → 腿长
 
-             'belly_belt'=>'',         // 净肚围 → 肚围
 
-             'shoulder_width'=>'', // 净肩宽 → 肩宽
 
-             'leg_root'=>'',        // 净腿根 → 大腿围
 
-             'neck'=>'',          // 净颈围 → 颈围                    
 
-             'inseam'=>'',      // 内腿长 → 内腿长                             
 
-             'upper_arm'=>'',     // 净上臂围 → 上臂围                    
 
-             'ankle'=>'',        // 净脚踝围 → 脚踝围
 
-             'waist_lower'=>'',    // 净小腹围 → 下腰围
 
-             'mid_waist'=>'',    // 净中腰 → 中腰围
 
-             '_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 = [
 
-                     'xiongwei' => 'chest',        // 净胸围 → 胸围
 
-                     'yaowei' => 'waist',          // 净腰围 → 腰围
 
-                     'tunwei' => 'hip',           // 净臀围 → 实际臀围         
 
-                     //'datuigen' => 'thigh',        // 净腿根 → 大腿围
 
-                     'xigai' => 'knee',            // 净膝围 → 膝围
 
-                     'xiaotuiwei' => 'calf',       // 净小腿围 → 小腿围                  
 
-                     'shoubichang' => 'arm_length', // 净手臂长 → 臂长
 
-                     'shouwanwei' => 'wrist',      // 净手腕围 → 手腕围                  
 
-                     'tuichang' => 'pants_length',   // 腿长 → 腿长
 
-                     'duwei' => 'belly_belt',         // 净肚围 → 肚围
 
-                     'jiankuan' => 'shoulder_width', // 净肩宽 → 肩宽
 
-                     'datuigen' => 'leg_root',        // 净腿根 → 大腿围
 
-                     'jingwei' => 'neck',          // 净颈围 → 颈围                    
 
-                     'neitui' => 'inseam',      // 内腿长 → 内腿长                             
 
-                     'shangbi' => 'upper_arm',     // 净上臂围 → 上臂围                    
 
-                     'jiaohuai' => 'ankle',        // 净脚踝围 → 脚踝围
 
-                     'xiaofu' => 'waist_lower',    // 净小腹围 → 下腰围
 
-                     'zhongyao' => 'mid_waist',    // 净中腰 → 中腰围
 
-                 ];
 
-                 
 
-                 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)
 
-     {
 
-         try {
 
-             // 更新任务状态为处理中
 
-             \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('id', $taskId)
 
-                 ->update([
 
-                     'status' => 1,
 
-                     'started_at' => time(),
 
-                     'attempts' => \think\Db::raw('attempts + 1'),
 
-                     'updatetime' => time()
 
-                 ]);
 
-             // 获取任务详情
 
-             $task = \think\Db::table('fa_ai_measurement_task')->where('id', $taskId)->find();
 
-             $photos = json_decode($task['photos'], true);
 
-             $params = json_decode($task['params'], true);
 
-             // 安全调用第三方AI分析服务 - 确保身高为数字格式
 
-             $heightCm = is_numeric($params['height']) ? floatval($params['height']) : 0;
 
-             $measurements = $this->safeCallThirdPartyAiService(
 
-                 $photos,
 
-                 $heightCm
 
-             );
 
-             // 处理结果
 
-             $result = [
 
-                 'measurements' => $measurements,
 
-                 'confidence' => $measurements['_confidence'] ?? 0.8,
 
-                 'warnings' => $measurements['_warnings'] ?? []
 
-             ];
 
-             // 清理内部字段
 
-             unset($result['measurements']['_confidence']);
 
-             unset($result['measurements']['_warnings']);
 
-             // 更新任务状态为完成
 
-             \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('id', $taskId)
 
-                 ->update([
 
-                     'status' => 2,
 
-                     'result' => json_encode($result),
 
-                     'completed_at' => time(),
 
-                     'updatetime' => time()
 
-                 ]);
 
-         } catch (\Exception $e) {
 
-             // 更新任务状态为失败
 
-             \think\Db::table('fa_ai_measurement_task')
 
-                 ->where('id', $taskId)
 
-                 ->update([
 
-                     'status' => 3,
 
-                     'error_message' => $e->getMessage(),
 
-                     'updatetime' => time()
 
-                 ]);
 
-         }
 
-     }
 
-     /**
 
-      * 估算处理进度
 
-      */
 
-     private function estimateProgress($task)
 
-     {
 
-         $startTime = $task['started_at'];
 
-         $currentTime = time();
 
-         $elapsedTime = $currentTime - $startTime;
 
-         // 假设总处理时间为30秒
 
-         $totalTime = 30;
 
-         $progress = min(95, ($elapsedTime / $totalTime) * 100);
 
-         return round($progress);
 
-     }
 
-     /**
 
-      * 格式化测量结果用于展示
 
-      */
 
-     private function formatMeasurementResult($result, $profileId)
 
-     {
 
-         $profile = \app\common\model\BodyProfile::find($profileId);
 
-         $measurements = $result['measurements'];
 
-         // 获取显示配置
 
-         $displayConfig = AiMeasurementService::getMeasurementDisplayConfig($profile->gender);
 
-         // 格式化数据
 
-         $formattedData = [
 
-             'profile' => [
 
-                 'id' => $profile->id,
 
-                 'name' => $profile->profile_name,
 
-                 'gender' => $profile->gender,
 
-                 'height' => $profile->height,
 
-                 'weight' => $profile->weight
 
-             ],
 
-             'measurements' => [],
 
-             'display_config' => $displayConfig,
 
-             'confidence' => $result['confidence'] ?? 0,
 
-             'warnings' => $result['warnings'] ?? []
 
-         ];
 
-         // 组织测量数据
 
-         foreach ($displayConfig as $field => $config) {
 
-             $value = isset($measurements[$field]) && $measurements[$field] > 0 
 
-                 ? $measurements[$field] 
 
-                 : null;
 
-             $formattedData['measurements'][$field] = [
 
-                 'label' => $config['label'],
 
-                 'value' => $value,
 
-                 'unit' => 'cm',
 
-                 'position' => $config['position'],
 
-                 'side' => $config['side']
 
-             ];
 
-         }
 
-         // 添加基础数据表格
 
-         $formattedData['basic_data'] = [
 
-             ['label' => '身高', 'value' => $profile->height, 'unit' => 'cm'],
 
-             ['label' => '体重', 'value' => $profile->weight, 'unit' => 'kg'],
 
-         ];
 
-         // 添加测量数据表格
 
-         $tableData = [];
 
-         $fields = array_keys($measurements);
 
-         $chunks = array_chunk($fields, 2);
 
-         foreach ($chunks as $chunk) {
 
-             $row = [];
 
-             foreach ($chunk as $field) {
 
-                 if (isset($displayConfig[$field])) {
 
-                     $row[] = [
 
-                         'label' => $displayConfig[$field]['label'],
 
-                         'value' => $measurements[$field] ?? null,
 
-                         'unit' => 'cm'
 
-                     ];
 
-                 }
 
-             }
 
-             if (!empty($row)) {
 
-                 $tableData[] = $row;
 
-             }
 
-         }
 
-         $formattedData['table_data'] = $tableData;
 
-         return $formattedData;
 
-     }
 
- }
 
 
  |