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测量任务 */ 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分析服务 $measurements = AiMeasurementService::analyzBodyPhotos( $photos, $params['gender'], $params['height'], $params['weight'] ); // 处理结果 $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; } }