| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- <?php
- namespace app\service;
- use app\service\AIGatewayService;
- use think\Db;
- use think\Exception;
- use think\Queue;
- /**
- * ImageService 图像任务服务
- * 负责图生文、文生文、文生图、图生图等 AI 任务的队列派发与状态管理
- */
- class ImageService
- {
- /** 队列名称 */
- private const QUEUE_ARRIMAGE = 'arrimage';
- /** Redis 任务状态 TTL(秒) */
- private const TASK_TTL = 300;
- /** Redis key 前缀 */
- private const KEY_TEXT_TO_IMAGE = 'text_to_image_task:';
- private const KEY_IMG_TO_IMG = 'img_to_img_task:';
- /**
- * 图生文:提交到队列
- * @param array $params 请求参数
- * @return bool
- */
- public function handleImgToText(array $params): bool
- {
- Queue::push('app\job\ImageArrJob', $params, self::QUEUE_ARRIMAGE);
- return true;
- }
- /**
- * 文生文:直接调用 API 并返回结果
- * @param string $prompt 提示词
- * @param string $model 模型名称
- * @return array ['success'=>bool, 'message'=>string, 'data'=>string]
- */
- public function handleTextToText($status_val, string $prompt, string $model): array
- {
- $ai = new AIGatewayService();
- $gptRes = $ai->buildRequestData($status_val, $model, $prompt);
- $gptText = '';
- if (isset($gptRes['candidates'][0]['content']['parts'][0]['text'])) {
- $gptText = $gptRes['candidates'][0]['content']['parts'][0]['text'];
- } elseif (isset($gptRes['choices'][0]['message']['content'])) {
- $gptText = trim($gptRes['choices'][0]['message']['content']);
- }
- if (isset($gptRes['error']) || isset($gptRes['code']) && $gptRes['code'] !== 0) {
- return [
- 'success' => false,
- 'message' => $gptRes['msg'] ?? $gptRes['error']['message'] ?? '生成失败',
- 'data' => ''
- ];
- }
- return [
- 'success' => true,
- 'message' => '生成成功',
- 'data' => $gptText
- ];
- }
- /**
- * 文生图:创建任务并推送到队列
- * @param array $params 含 id、model 等
- * @return array ['success'=>bool, 'message'=>string, 'task_id'=>string]
- */
- public function handleTextToImg(array $params): array
- {
- return $this->submitTaskToQueue(
- $params,
- self::KEY_TEXT_TO_IMAGE,
- '正在生成图片中,请稍等.....'
- );
- }
- /**
- * 图生图:创建任务并推送到队列(产品图+模板图)
- * @param array $params 含 id、product_img、template_img、prompt、model 等
- * @return array ['success'=>bool, 'message'=>string, 'task_id'=>string]
- */
- public function handleImgToImg(array $params): array
- {
- return $this->submitTaskToQueue(
- $params,
- self::KEY_IMG_TO_IMG,
- '正在生成图片中,请稍等.....'
- );
- }
- /**
- * 通用:创建任务 ID、写入 Redis、推送到队列
- */
- private function submitTaskToQueue(array $params, string $redisKeyPrefix, string $message): array
- {
- $taskId = ($params['id'] ?? '0') . '-' . date('YmdHis') . '-' . mt_rand(1000, 9999);
- $params['task_id'] = $taskId;
- $redis = getTaskRedis();
- $redis->set($redisKeyPrefix . $taskId, json_encode([
- 'status' => 'pending',
- 'created_at' => date('Y-m-d H:i:s')
- ]), ['EX' => self::TASK_TTL]);
- Queue::push('app\job\ImageArrJob', $params, self::QUEUE_ARRIMAGE);
- return [
- 'success' => true,
- 'message' => $message,
- 'task_id' => $taskId
- ];
- }
- /**
- * 批量图像任务:支持链式任务和单类型任务
- * @param array $params 含 batch、num、type、old_image_file 等
- * @return bool
- */
- public function handleImage(array $params): bool
- {
- if (!isset($params['batch']) || !is_array($params['batch'])) {
- return false;
- }
- $arr = $this->buildBatchItems($params);
- $insertData = $this->buildQueueLogData($params, count($arr));
- if (empty($params['type'])) {
- $this->dispatchFullChainTask($arr, $insertData);
- } else {
- $result = $this->dispatchSingleTypeTask($arr, $params, $insertData);
- if (!$result) {
- return false;
- }
- }
- return true;
- }
- /** 构建批量任务项 */
- private function buildBatchItems(array $params): array
- {
- $arr = [];
- foreach ($params['batch'] as $v) {
- $baseItem = [
- 'sourceDir' => $this->sourceDir($v, 1),
- 'outputDir' => $this->sourceDir($v, 2),
- 'file_name' => $this->sourceDir($v, 3),
- 'type' => $params['type'] ?? '',
- 'selectedOption' => $params['selectedOption'] ?? '',
- 'txttotxt_selectedOption' => $params['txttotxt_selectedOption'] ?? '',
- 'imgtotxt_selectedOption' => $params['imgtotxt_selectedOption'] ?? '',
- 'prompt' => '',
- 'width' => $params['width'] ?? 0,
- 'height' => $params['height'] ?? 0,
- 'executeKeywords' => $params['executeKeywords'] ?? '',
- 'sys_id' => $params['sys_id'] ?? ''
- ];
- $num = (int)($params['num'] ?? 1);
- $arr = array_merge($arr, array_fill(0, max(1, $num), $baseItem));
- }
- return $arr;
- }
- /** 构建队列日志插入数据 */
- private function buildQueueLogData(array $params, int $imageCount): array
- {
- return [
- 'create_time' => date('Y-m-d H:i:s'),
- 'old_image_file' => $params['old_image_file'] ?? '',
- 'status' => '等待中',
- 'image_count' => $imageCount,
- 'params' => json_encode($params, JSON_UNESCAPED_UNICODE)
- ];
- }
- /** 派发一键链式任务:图生文→文生文→文生图→图生图→高清放大 */
- private function dispatchFullChainTask(array $arr, array $insertData): void
- {
- $params = json_decode($insertData['params'], true) ?: [];
- $insertData['model'] = 'gpt-4-vision-preview,gpt-4,' . ($params['selectedOption'] ?? '');
- $insertData['model_name'] = '文生图';
- $taskId = Db::name('queue_logs')->insertGetId($insertData);
- $arr = array_map(function ($item) use ($taskId) {
- $item['type'] = '图生文';
- $item['chain_next'] = ['文生文', '文生图', '图生图', '高清放大'];
- $item['task_id'] = $taskId;
- return $item;
- }, $arr);
- Queue::push('app\job\ImageArrJob', ['task_id' => $taskId, 'data' => $arr], self::QUEUE_ARRIMAGE);
- }
- /** 派发单类型任务 */
- private function dispatchSingleTypeTask(array $arr, array $params, array $insertData): bool
- {
- $typeConfig = $this->getTypeConfig($params['type'], $params);
- if (!$typeConfig) {
- return false;
- }
- $insertData['model'] = $typeConfig['model'];
- $insertData['model_name'] = $typeConfig['model_name'];
- $taskId = Db::name('queue_logs')->insertGetId($insertData);
- $arr = array_map(function ($item) use ($params, $taskId) {
- $item['type'] = $params['type'];
- $item['task_id'] = $taskId;
- return $item;
- }, $arr);
- Queue::push('app\job\ImageArrJob', ['task_id' => $taskId, 'data' => $arr], self::QUEUE_ARRIMAGE);
- return true;
- }
- /** 任务类型对应的 model 配置 */
- private function getTypeConfig(string $type, array $params = []): ?array
- {
- $configs = [
- '图生文' => ['model' => 'gpt-4-vision-preview', 'model_name' => '图生文'],
- '文生文' => ['model_key' => 'txttotxt_selectedOption', 'model_name' => '文生文'],
- '文生图' => ['model_key' => 'selectedOption', 'model_name' => '文生图'],
- '图生图' => ['model' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]', 'model_name' => '图生图'],
- '高清放大' => ['model' => '高清放大', 'model_name' => '高清放大']
- ];
- $cfg = $configs[$type] ?? null;
- if (!$cfg) {
- return null;
- }
- if (isset($cfg['model_key'])) {
- $cfg['model'] = $params[$cfg['model_key']] ?? '';
- unset($cfg['model_key']);
- }
- return $cfg;
- }
- /**
- * 解析图像路径
- * @param string $filePath 如 uploads/operate/ai/Preview/20240610/xxx.png
- * @param int $type 1=源目录 2=输出目录 3=文件名
- * @return string|null
- */
- public function sourceDir(string $filePath, int $type): ?string
- {
- $pathParts = explode('/', $filePath);
- $filename = array_pop($pathParts);
- $baseParts = $pathParts;
- $date = '';
- foreach ($pathParts as $index => $part) {
- if (preg_match('/^\d{8}$/', $part)) {
- $date = $part;
- unset($baseParts[$index]);
- break;
- }
- }
- $basePath = implode('/', $baseParts);
- switch ($type) {
- case 1:
- return $basePath;
- case 2:
- return '/' . str_replace('/Preview/', '/dall-e/', $basePath) . $date;
- case 3:
- return $filename;
- default:
- return null;
- }
- }
- }
|