123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- <?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身体测量分析
- */
- 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测量任务
- */
- 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;
- }
- }
|