| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910 | <?phpnamespace 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 (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;    }}
 |