Prechádzať zdrojové kódy

1.新增火山图片生成API模型的接入2.历史所有创建图片的任务都支持多模型API

lh 3 mesiacov pred
rodič
commit
a9ed9bc04d

+ 0 - 52
.env.example

@@ -1,52 +0,0 @@
-APP_NAME=Laravel
-APP_ENV=local
-APP_KEY=
-APP_DEBUG=true
-APP_URL=http://localhost
-
-LOG_CHANNEL=stack
-LOG_DEPRECATIONS_CHANNEL=null
-LOG_LEVEL=debug
-
-DB_CONNECTION=mysql
-DB_HOST=127.0.0.1
-DB_PORT=3306
-DB_DATABASE=laravel
-DB_USERNAME=root
-DB_PASSWORD=
-
-BROADCAST_DRIVER=log
-CACHE_DRIVER=file
-FILESYSTEM_DRIVER=local
-QUEUE_CONNECTION=sync
-SESSION_DRIVER=file
-SESSION_LIFETIME=120
-
-MEMCACHED_HOST=127.0.0.1
-
-REDIS_HOST=127.0.0.1
-REDIS_PASSWORD=null
-REDIS_PORT=6379
-
-MAIL_MAILER=smtp
-MAIL_HOST=mailhog
-MAIL_PORT=1025
-MAIL_USERNAME=null
-MAIL_PASSWORD=null
-MAIL_ENCRYPTION=null
-MAIL_FROM_ADDRESS=null
-MAIL_FROM_NAME="${APP_NAME}"
-
-AWS_ACCESS_KEY_ID=
-AWS_SECRET_ACCESS_KEY=
-AWS_DEFAULT_REGION=us-east-1
-AWS_BUCKET=
-AWS_USE_PATH_STYLE_ENDPOINT=false
-
-PUSHER_APP_ID=
-PUSHER_APP_KEY=
-PUSHER_APP_SECRET=
-PUSHER_APP_CLUSTER=mt1
-
-MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
-MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

+ 38 - 4
app/Console/Commands/CheckImageGenerationTasksCommand.php

@@ -5,6 +5,7 @@ namespace App\Console\Commands;
 use App\Services\AIGeneration\AIImageGenerationService;
 use Illuminate\Console\Command;
 use Illuminate\Support\Facades\DB;
+use App\Consts\BaseConst;
 
 class CheckImageGenerationTasksCommand extends Command
 {
@@ -35,14 +36,47 @@ class CheckImageGenerationTasksCommand extends Command
         // 执行50s
         $time_start = time();
         try {
-            $count = DB::table('mp_generate_pic_tasks')->where('status', 'processing')->count('id');
-            while ($count > 0) {
+            // 统计需要处理的任务数量(即梦AI的processing + 火山API的pending和processing)
+            $jimengProcessingCount = DB::table('mp_generate_pic_tasks')
+                ->where('status', 'processing')
+                ->where('model', 'jimeng_4.0')
+                ->count('id');
+            
+            $volcPendingCount = DB::table('mp_generate_pic_tasks')
+                ->where('status', 'pending')
+                ->whereIn('model', BaseConst::VOLC_PIC_MODELS)
+                ->count('id');
+            
+            $totalCount = $jimengProcessingCount + $volcPendingCount;
+            
+            dLog('generate')->info("待处理任务统计 - 即梦AI处理中: {$jimengProcessingCount}, 火山API待处理: {$volcPendingCount}");
+            
+            while ($totalCount > 0) {
                 $time_diff = time() - $time_start;
+                if ($time_diff > 50) {
+                    dLog('generate')->info('定时任务执行超过50秒,退出循环');
+                    break;
+                }
+                
                 sleep(3);
-                if ($time_diff > 50) break;
+                
+                // 执行任务更新
                 $aiImageGenerationService->updatePendingTasks();
-                $count = DB::table('mp_generate_pic_tasks')->where('status', 'processing')->count('id');
+                
+                // 重新统计任务数量
+                $jimengProcessingCount = DB::table('mp_generate_pic_tasks')
+                    ->where('status', 'processing')
+                    ->where('model', 'jimeng_4.0')
+                    ->count('id');
+                
+                $volcPendingCount = DB::table('mp_generate_pic_tasks')
+                    ->where('status', 'pending')
+                    ->whereIn('model', BaseConst::VOLC_PIC_MODELS)
+                    ->count('id');
+                
+                $totalCount = $jimengProcessingCount + $volcPendingCount;
             }
+            
             dLog('generate')->info('任务状态检查完成');
         } catch (\Exception $e) {
             dLog('generate')->error('任务状态检查失败: ' . $e->getMessage());

+ 2 - 0
app/Consts/BaseConst.php

@@ -81,4 +81,6 @@ class BaseConst
         'QUARTER'   => '季卡',
         'YEAR'      => '年卡'
     ];
+
+    const VOLC_PIC_MODELS = ['doubao-seedream-5-0-260128', 'doubao-seedream-5-0-lite-260128', 'doubao-seedream-4-5-251128'];
 }

+ 4 - 0
app/Http/Controllers/AIGeneration/ImageGenerationController.php

@@ -11,6 +11,8 @@ use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Facades\Validator;
 use App\Libs\ApiResponse;
 
+use App\Consts\BaseConst;
+
 class ImageGenerationController extends BaseController
 {
     use ApiResponse;
@@ -39,6 +41,7 @@ class ImageGenerationController extends BaseController
             '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 = [
@@ -56,6 +59,7 @@ class ImageGenerationController extends BaseController
             'scale.required' => '缩放比例不能为空',
             'scale.numeric' => '缩放比例必须是数字',
             'scale.between' => '缩放比例必须在0到100之间',
+            'model.in' => '模型类型无效',
         ];
 
         // 根据传入参数决定验证规则

+ 2 - 1
app/Models/MpGeneratePicTask.php

@@ -27,7 +27,8 @@ class MpGeneratePicTask extends Model
         'completed_at',
         'created_at',
         'updated_at',
-        'result_json'
+        'result_json',
+        'model'
     ];
 
     protected $casts = [

+ 227 - 12
app/Services/AIGeneration/AIImageGenerationService.php

@@ -9,12 +9,15 @@ use GuzzleHttp\Exception\GuzzleException;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Redis;
+use App\Consts\BaseConst;
 
 class AIImageGenerationService
 {
     private $volcEngineService;
     private $httpClient;
 
+    // 模型常量
+    const MODEL_JIMENG_4 = 'jimeng_4.0';
 
     public function __construct(VolcEngineService $volcEngineService)
     {
@@ -32,8 +35,11 @@ class AIImageGenerationService
      */
     public function createImageGenerationTask(array $params): MpGeneratePicTask
     {
-        // 检查是否有正在处理的任务
-        if ($this->hasProcessingTask()) {
+        // 获取模型类型,默认使用即梦AI 4.0
+        $model = $params['model'] ?? self::MODEL_JIMENG_4;
+        
+        // 检查是否有正在处理的任务(仅即梦AI需要排队)
+        if ($model === self::MODEL_JIMENG_4 && $this->hasProcessingTask()) {
             // 如果有正在处理的任务,创建排队任务
             return $this->createQueuedTask($params);
         }
@@ -54,10 +60,13 @@ class AIImageGenerationService
             'mask_img_url' => $params['mask_img_url'] ?? null,
             'extra_params' => $params['extra_params'] ?? null,
             'status' => MpGeneratePicTask::STATUS_PENDING,
+            'model' => $model,
         ]);
 
-        // 发送异步请求到即梦AI API创建任务
-        $this->submitTaskToApi($task);
+        // 仅即梦AI立即提交任务,火山API在定时任务中提交
+        if ($model === self::MODEL_JIMENG_4) {
+            $this->submitTaskToJimengApi($task);
+        }
 
         return $task;
     }
@@ -83,6 +92,9 @@ class AIImageGenerationService
     {
         // 生成唯一的任务ID
         $taskId = 'img_gen_' . time() . '_' . uniqid();
+        
+        // 获取模型类型
+        $model = $params['model'] ?? self::MODEL_JIMENG_4;
 
         // 创建排队状态的任务记录
         return MpGeneratePicTask::create([
@@ -96,7 +108,8 @@ class AIImageGenerationService
             'ref_img_url' => $params['ref_img_urls'] ?? null,
             'mask_img_url' => $params['mask_img_url'] ?? null,
             'extra_params' => $params['extra_params'] ?? null,
-            'status' => MpGeneratePicTask::STATUS_PENDING, // 保持PENDING状态,等待处理
+            'status' => MpGeneratePicTask::STATUS_PENDING,
+            'model' => $model,
         ]);
     }
 
@@ -106,7 +119,7 @@ class AIImageGenerationService
      * @param MpGeneratePicTask $task
      * @return void
      */
-    private function submitTaskToApi(MpGeneratePicTask $task): void
+    private function submitTaskToJimengApi(MpGeneratePicTask $task): void
     {
         try {
             // 验证环境变量配置
@@ -149,7 +162,7 @@ class AIImageGenerationService
                 $apiParams['size'] = (int)$area;
             }
 
-            dLog('generate')->info('文生图参数: ', $apiParams);
+            dLog('generate')->info('即梦AI文生图参数: ', $apiParams);
             
             //调用即梦AI 4.0 API提交任务
             $response = $this->volcEngineService->request(
@@ -195,6 +208,171 @@ class AIImageGenerationService
         }
     }
 
+    /**
+     * 提交任务到火山图片生成API
+     *
+     * @param MpGeneratePicTask $task
+     * @return void
+     */
+    private function submitTaskToVolcApi(MpGeneratePicTask $task): void
+    {
+        try {
+            // 验证环境变量配置
+            $apiKey = env('VOLC_AI_API_KEY');
+                
+            if (empty($apiKey)) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '火山引擎API KEY未配置,请检查环境变量VOLC_AI_API_KEY'
+                ]);
+                return;
+            }
+            
+            // 更新状态为处理中
+            $task->updateStatus(MpGeneratePicTask::STATUS_PROCESSING);
+                
+            // 构建火山图片生成API请求参数
+            $apiParams = [
+                'model' => $task->model,
+                'prompt' => $task->prompt,
+                'watermark' => false,
+            ];
+            
+            // 尺寸参数
+            if ($task->width && $task->height) {
+                $apiParams['size'] = $task->width . 'x' . $task->height;
+            }
+            
+            // 参考图片
+            if ($task->ref_img_url) {
+                if (is_array($task->ref_img_url)) {
+                    // $apiParams['image'] = count($task->ref_img_url) === 1 ? $task->ref_img_url[0] : $task->ref_img_url;
+                    $apiParams['image'] = $task->ref_img_url;
+                } else {
+                    $apiParams['image'] = $task->ref_img_url;
+                }
+            }
+
+            // 生成多图或者单图
+            if ((int)$task->image_num === 1) {
+                $apiParams['sequential_image_generation'] = 'disabled';
+            }else if ($task->image_num > 1) {
+                $apiParams['sequential_image_generation'] = 'auto';
+                $apiParams['sequential_image_generation_options']['max_images'] = $task->image_num;
+            }
+
+            dLog('generate')->info('火山图片生成API参数: ', $apiParams);
+            
+            // 调用火山图片生成API
+            $response = $this->httpClient->post('https://ark.cn-beijing.volces.com/api/v3/images/generations', [
+                'headers' => [
+                    'Authorization' => 'Bearer ' . $apiKey,
+                    'Content-Type' => 'application/json',
+                ],
+                'json' => $apiParams,
+            ]);
+    
+            $responseData = json_decode($response->getBody()->getContents(), true);
+            dLog('generate')->info('火山图片生成API响应: ', $responseData);
+                
+            if (isset($responseData['error'])) {
+                // API返回错误
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => $responseData['error']['message'] ?? 'API Error',
+                    'result_json' => $responseData
+                ]);
+            } else {
+                // 任务提交成功,直接处理结果
+                $this->processVolcApiResponse($task, $responseData);
+            }
+        } catch (\Exception $e) {
+            // 记录错误
+            dLog('generate')->error('火山API请求失败: ' . $e->getMessage());
+            $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                'error_message' => 'API请求失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 处理火山API响应结果
+     *
+     * @param MpGeneratePicTask $task
+     * @param array $responseData
+     * @return void
+     */
+    private function processVolcApiResponse(MpGeneratePicTask $task, array $responseData): void
+    {
+        try {
+            if (!isset($responseData['data']) || empty($responseData['data'])) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '未返回图片数据',
+                    'result_json' => $responseData
+                ]);
+                return;
+            }
+
+            $result_urls = [];
+            foreach ($responseData['data'] as $imageData) {
+                if (isset($imageData['error'])) {
+                    // 单张图片生成失败
+                    dLog('generate')->warning('单张图片生成失败: ', $imageData['error']);
+                    continue;
+                }
+
+                if (isset($imageData['url'])) {
+                    $url = $imageData['url'];
+                    $pic_name = 'ai_generation_' . time() . '_' . uniqid();
+                    $pic_ext = getImgExtFromUrl($url);
+                    $pic_name = $pic_name . $pic_ext;
+                    
+                    if ($pic_ext === '.png') {
+                        $comporessed_data = compressRemoteImageUrlToSize($url);
+                        $url = uploadStreamByTos('image', $comporessed_data, $pic_name);
+                    } else {
+                        // 将图片另存到tos
+                        $url = uploadStreamByTos('image', file_get_contents($url), $pic_name);
+                    }
+                    
+                    $result_urls[] = $url;
+                }
+            }
+
+            if (empty($result_urls)) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '所有图片生成失败',
+                    'result_json' => $responseData
+                ]);
+                return;
+            }
+
+            // 更新任务状态为成功
+            $task->updateStatus(MpGeneratePicTask::STATUS_SUCCESS, [
+                'result_url' => $result_urls,
+                'result_json' => $responseData
+            ]);
+
+            // 同步调整分镜图片状态和结果
+            $segment_id = getProp($task, 'alias_segment_id');
+            if ($segment_id) {
+                DB::table('mp_episode_segments')->where('segment_id', $segment_id)->update([
+                    'img_url' => $result_urls[0],
+                    'pic_task_status' => '已完成',
+                ]);
+                
+                // 如果是首帧图ID则更新anime表
+                if (Redis::sismember('anime_first_frame_urls', $segment_id)) {
+                    $anime_id = DB::table('mp_episode_segments')->where('segment_id', $segment_id)->value('anime_id');
+                    DB::table('mp_animes')->where('id', $anime_id)->update(['first_frame_url' => $result_urls[0], 'updated_at' => date('Y-m-d H:i:s')]);
+                    Redis::srem('anime_first_frame_urls', $segment_id);
+                }
+            }
+        } catch (\Exception $e) {
+            $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                'error_message' => '处理响应失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
     public function getTaskList($data)
     {
         $task_id = getProp($data, 'task_id');
@@ -219,6 +397,28 @@ class AIImageGenerationService
      */
     public function queryTaskStatus(MpGeneratePicTask $task): array
     {
+        // 根据模型类型调用不同的查询方法
+        if ($task->model === self::MODEL_JIMENG_4) {
+            return $this->queryJimengTaskStatus($task);
+        } else {
+            // 火山API是同步返回结果,不需要查询
+            return [
+                'status' => $task->status === MpGeneratePicTask::STATUS_SUCCESS ? 'success' : 'failed',
+                'error_message' => $task->error_message,
+                'result_url' => $task->result_url,
+                'result_json' => $task->result_json ?? []
+            ];
+        }
+    }
+
+    /**
+     * 查询即梦AI任务状态
+     *
+     * @param MpGeneratePicTask $task
+     * @return array
+     */
+    private function queryJimengTaskStatus(MpGeneratePicTask $task): array
+    {
         try {
             // 获取API任务ID
             $apiTaskId = $task->task_id ?? null;
@@ -325,8 +525,22 @@ class AIImageGenerationService
     public function updatePendingTasks(): void
     {
         dLog('generate')->info('更新图片状态ing');
-        // 获取所有处理中的任务
+        
+        // 1. 处理火山API的pending任务(批量提交)
+        $volcPendingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PENDING)
+            ->whereIn('model', BaseConst::VOLC_PIC_MODELS)
+            ->orderBy('created_at', 'asc')
+            ->limit(50) // 每次最多处理50个任务,避免超时
+            ->get();
+
+        foreach ($volcPendingTasks as $task) {
+            dLog('generate')->info('开始处理火山API任务: ' . $task->task_id);
+            $this->submitTaskToVolcApi($task);
+        }
+        
+        // 2. 获取所有处理中的任务(仅即梦AI需要查询状态)
         $processingTasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PROCESSING)
+            ->where('model', self::MODEL_JIMENG_4)
             ->get();
 
         foreach ($processingTasks as $task) {
@@ -376,7 +590,7 @@ class AIImageGenerationService
             }
         }
 
-        // 检查是否有任务完成,如果有则处理下一个排队任务
+        // 3. 检查是否有任务完成,如果有则处理下一个排队任务(仅即梦AI)
         $this->processNextQueuedTask();
     }
 
@@ -387,19 +601,20 @@ class AIImageGenerationService
      */
     public function processNextQueuedTask(): void
     {
-        // 检查是否还有正在处理的任务
+        // 检查是否还有正在处理的任务(仅即梦AI)
         if ($this->hasProcessingTask()) {
             return; // 如果还有处理中的任务,不处理新任务
         }
 
-        // 获取最早的待处理任务
+        // 获取最早的待处理任务(仅即梦AI)
         $nextTask = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PENDING)
+            ->where('model', self::MODEL_JIMENG_4)
             ->orderBy('created_at', 'asc')
             ->first();
 
         if ($nextTask) {
             dLog('generate')->info('开始处理排队任务: ' . $nextTask->task_id);
-            $this->submitTaskToApi($nextTask);
+            $this->submitTaskToJimengApi($nextTask);
         }
     }