aiImageGenerationService = $aiImageGenerationService; } /** * 创建图片生成任务 * * @param Request $request * @return JsonResponse */ public function createTask(Request $request): JsonResponse { $data = $request->all(); // 首先验证基本参数 $baseRules = [ 'prompt' => 'required|string|max:2000', 'width' => 'required|numeric|between:1024,4096', 'height' => 'required|numeric|between:1024,4096', 'image_num' => 'required|numeric|between:1,4', 'scale' => 'required|numeric|between:0,100', 'model' => 'nullable|string|in:jimeng_4.0,' . implode(',', BaseConst::VOLC_PIC_MODELS), ]; $baseMessages = [ 'prompt.required' => '提示词不能为空', 'prompt.max' => '提示词不能超过2000个字符', 'width.required' => '宽不能为空', 'width.numeric' => '宽必须是数字', 'width.between' => '宽必须在1024到4096之间', 'height.required' => '高不能为空', 'height.numeric' => '高必须是数字', 'height.between' => '高必须在1024到4096之间', 'image_num.required' => '生成图片数量不能为空', 'image_num.numeric' => '生成图片数量必须是数字', 'image_num.between' => '生成图片数量必须在1到4之间', 'scale.required' => '缩放比例不能为空', 'scale.numeric' => '缩放比例必须是数字', 'scale.between' => '缩放比例必须在0到100之间', 'model.in' => '模型类型无效', ]; // 根据传入参数决定验证规则 if (isset($data['ref_img_url'])) { // URL 方式 - 支持单个或多个URL(逗号分隔或数组) $validationRules = array_merge($baseRules, [ 'ref_img_url' => 'required', ]); $validationMessages = array_merge($baseMessages, [ 'ref_img_url.required' => '参考图片URL不能为空', ]); } else { // 上传文件方式 - 支持多个文件 $validationRules = array_merge($baseRules, [ 'ref_img_file' => 'required', 'ref_img_file.*' => 'image|mimes:jpeg,png|max:4800', ]); $validationMessages = array_merge($baseMessages, [ 'ref_img_file.required' => '参考图片不能为空', 'ref_img_file.*.image' => '参考图片必须是图片文件', 'ref_img_file.*.mimes' => '参考图片必须是jpeg,png格式', 'ref_img_file.*.max' => '参考图片大小不能超过4.7MB', ]); } $validator = Validator::make($data, $validationRules, $validationMessages); if ($validator->fails()) { Utils::throwError('1002:'.$validator->errors()->first()); } $refImgUrls = []; $refImgBase64List = []; // 处理上传的图片 if ($request->hasFile('ref_img_file')) { $refImgFiles = is_array($request->file('ref_img_file')) ? $request->file('ref_img_file') : [$request->file('ref_img_file')]; foreach ($refImgFiles as $index => $refImgFile) { // 验证图片分辨率和比例 $refImgInfo = @getimagesize($refImgFile->getPathname()); if (!$refImgInfo) { Utils::throwError('1003:图片' . ($index + 1) . '格式无效'); } $refImg = [ 'width' => $refImgInfo[0], 'height' => $refImgInfo[1], 'size' => $refImgFile->getSize(), ]; // 验证图片规格 $this->validateImageSpecs($refImg, $index + 1); // 上传图片到服务器并获取URL $filename = randStr(10) . '.' . $refImgFile->getClientOriginalExtension(); $uploaded_url = uploadStreamByTos('image', file_get_contents($refImgFile->getPathname()), $filename); if (!$uploaded_url) { Utils::throwError('1003:图片' . ($index + 1) . '保存失败'); } $refImgUrls[] = $uploaded_url; } } else { // 使用URL方式,支持逗号分隔的字符串或数组 $urls = is_array($data['ref_img_url']) ? $data['ref_img_url'] : array_map('trim', explode(',', $data['ref_img_url'])); foreach ($urls as $index => $url) { if (empty($url)) { continue; } // 验证URL格式 if (!filter_var($url, FILTER_VALIDATE_URL)) { Utils::throwError('1003:图片URL' . ($index + 1) . '格式无效'); } // 验证远程图片 $refImg = Utils::getRemoteImageInfo($url, true); if (!$refImg) { Utils::throwError('1003:图片' . ($index + 1) . '获取失败'); } // 验证图片规格 $this->validateImageSpecs($refImg, $index + 1); $refImgUrls[] = $url; } } if (empty($refImgUrls)) { Utils::throwError('1003:至少需要一张参考图片'); } // 将处理后的URL存入data(支持多个) $data['ref_img_urls'] = $refImgUrls; $data['ref_img_url'] = $refImgUrls[0]; // 保持向后兼容 try { $task = $this->aiImageGenerationService->createImageGenerationTask($data); return $this->success(['task_id' => $task->id, 'status' => $task->status]); } catch (\Exception $e) { Utils::throwError('1001:'.$e->getMessage()); } } /** * 验证图片规格 * * @param array $refImg * @param int $index * @return void */ private function validateImageSpecs(array $refImg, int $index): void { // 判断图片是否满足以下条件: // 1.图片文件最大4.7MB // 2.图片分辨率最大: 4096*4096, 最短边不低于320 // 3.图片长边与短边比例在3以内 if ($refImg['size'] > 13 * 1024 * 1024) { Utils::throwError('1003:图片' . $index . '大小不能超过10MB'); } $minDimension = min($refImg['width'], $refImg['height']); $maxDimension = max($refImg['width'], $refImg['height']); if ($maxDimension > 4096 || $minDimension < 320) { Utils::throwError('1003:图片' . $index . '分辨率不能超过4096*4096,且最短边不能低于320'); } if ($maxDimension / $minDimension > 3) { Utils::throwError('1003:图片' . $index . '长边与短边比例不能超过3'); } } /** * 查询任务状态 * * @param string $taskId * @return JsonResponse */ public function taskStatus(string $taskId): JsonResponse { $task = \App\Models\MpGeneratePicTask::where('id', $taskId)->first(); if (!$task) { return response()->json([ 'success' => false, 'message' => '任务不存在' ], 404); } return response()->json([ 'success' => true, 'data' => [ 'task_id' => $task->id, 'status' => $task->status, 'result_url' => $task->result_url, 'error_message' => $task->error_message, 'created_at' => $task->created_at, 'started_at' => $task->started_at, 'completed_at' => $task->completed_at ] ]); } /** * 获取任务列表 * * @param Request $request * @return mixed */ public function taskList(Request $request) { $data = $request->all(); $result = $this->aiImageGenerationService->getTaskList($data); return $this->success($result, [new AIGenerationTransformer(), 'newBuildTaskList']); } /** * 批量上传图片或视频 * * @param Request $request * @return void */ public function batchUploadImg(Request $request) { $refImgUrls = []; $refVideoUrls = []; // 处理上传的图片文件 if ($request->hasFile('ref_img_file')) { $refImgFiles = is_array($request->file('ref_img_file')) ? $request->file('ref_img_file') : [$request->file('ref_img_file')]; foreach ($refImgFiles as $index => $refImgFile) { // 验证图片分辨率和比例 $refImgInfo = @getimagesize($refImgFile->getPathname()); if (!$refImgInfo) { Utils::throwError('1003:图片' . ($index + 1) . '格式无效'); } $refImg = [ 'width' => $refImgInfo[0], 'height' => $refImgInfo[1], 'size' => $refImgFile->getSize(), ]; // 验证图片规格 $this->validateImageSpecs($refImg, $index + 1); // 上传图片到服务器并获取URL $filename = randStr(10) . '.' . $refImgFile->getClientOriginalExtension(); $uploaded_url = uploadStreamByTos('image', file_get_contents($refImgFile->getPathname()), $filename); if (!$uploaded_url) { Utils::throwError('1003:图片' . ($index + 1) . '保存失败'); } $refImgUrls[] = $uploaded_url; } } // 处理图片二进制内容(ref_img数组) if ($request->has('ref_img')) { $refImgs = $request->input('ref_img'); // 确保是数组格式 if (!is_array($refImgs)) { $refImgs = [$refImgs]; } foreach ($refImgs as $index => $refImgBinary) { $tempFile = null; $tempPath = null; try { // 解码base64(如果是base64格式) if (preg_match('/^data:image\/(\w+);base64,/', $refImgBinary, $matches)) { $refImgBinary = base64_decode(substr($refImgBinary, strpos($refImgBinary, ',') + 1)); } // 创建临时文件以验证图片 $tempFile = tmpfile(); $tempPath = stream_get_meta_data($tempFile)['uri']; fwrite($tempFile, $refImgBinary); // 验证图片分辨率和比例 $refImgInfo = @getimagesize($tempPath); if (!$refImgInfo) { Utils::throwError('1003:图片' . ($index + 1) . '格式无效'); } $refImg = [ 'width' => $refImgInfo[0], 'height' => $refImgInfo[1], 'size' => strlen($refImgBinary), ]; // 验证图片规格 $this->validateImageSpecs($refImg, $index + 1); // 上传图片到服务器并获取URL(默认使用.jpg格式) $filename = randStr(10) . '.jpg'; $uploaded_url = uploadStreamByTos('image', $refImgBinary, $filename); if (!$uploaded_url) { Utils::throwError('1003:图片' . ($index + 1) . '保存失败'); } $refImgUrls[] = $uploaded_url; } finally { // 确保临时文件被关闭和删除 if ($tempFile) { fclose($tempFile); } } } } // 处理上传的视频文件 if ($request->hasFile('ref_video_file')) { $refVideoFiles = is_array($request->file('ref_video_file')) ? $request->file('ref_video_file') : [$request->file('ref_video_file')]; foreach ($refVideoFiles as $index => $refVideoFile) { // 验证视频文件大小(最大100MB) if ($refVideoFile->getSize() > 100 * 1024 * 1024) { Utils::throwError('1003:视频' . ($index + 1) . '大小不能超过100MB'); } // 验证视频文件类型 $allowedMimeTypes = ['video/mp4', 'video/mpeg', 'video/quicktime', 'video/x-msvideo']; if (!in_array($refVideoFile->getMimeType(), $allowedMimeTypes)) { Utils::throwError('1003:视频' . ($index + 1) . '格式无效,仅支持mp4、mpeg、mov、avi格式'); } // 上传视频到服务器并获取URL $filename = randStr(10) . '.' . $refVideoFile->getClientOriginalExtension(); $uploaded_url = uploadStreamByTos('video', file_get_contents($refVideoFile->getPathname()), $filename); if (!$uploaded_url) { Utils::throwError('1003:视频' . ($index + 1) . '保存失败'); } $refVideoUrls[] = $uploaded_url; } } // 处理视频二进制内容(ref_video数组) if ($request->has('ref_video')) { $refVideos = $request->input('ref_video'); // 确保是数组格式 if (!is_array($refVideos)) { $refVideos = [$refVideos]; } foreach ($refVideos as $index => $refVideoBinary) { try { // 解码base64(如果是base64格式) if (preg_match('/^data:video\/(\w+);base64,/', $refVideoBinary, $matches)) { $extension = $matches[1]; $refVideoBinary = base64_decode(substr($refVideoBinary, strpos($refVideoBinary, ',') + 1)); } else { $extension = 'mp4'; // 默认扩展名 } // 验证视频大小(最大100MB) if (strlen($refVideoBinary) > 100 * 1024 * 1024) { Utils::throwError('1003:视频' . ($index + 1) . '大小不能超过100MB'); } // 上传视频到服务器并获取URL $filename = randStr(10) . '.' . $extension; $uploaded_url = uploadStreamByTos('video', $refVideoBinary, $filename); if (!$uploaded_url) { Utils::throwError('1003:视频' . ($index + 1) . '保存失败'); } $refVideoUrls[] = $uploaded_url; } catch (\Exception $e) { Utils::throwError('1003:视频' . ($index + 1) . '处理失败:' . $e->getMessage()); } } } return $this->success([ 'image_urls' => $refImgUrls, 'video_urls' => $refVideoUrls ]); } }