Kaynağa Gözat

即梦AI图片生成4.0基本完成

lh 4 ay önce
ebeveyn
işleme
f7803eb164

+ 44 - 0
app/Console/Commands/CheckImageGenerationTasksCommand.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Services\AiImageGenerationService;
+use Illuminate\Console\Command;
+
+class CheckImageGenerationTasksCommand extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'image-generation:check-tasks';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '检查图片生成任务状态并更新';
+
+    /**
+     * Execute the console command.
+     *
+     * @param AiImageGenerationService $aiImageGenerationService
+     * @return int
+     */
+    public function handle(AiImageGenerationService $aiImageGenerationService)
+    {
+        $this->info('开始检查图片生成任务状态...');
+
+        try {
+            $aiImageGenerationService->updatePendingTasks();
+            $this->info('任务状态检查完成');
+        } catch (\Exception $e) {
+            $this->error('任务状态检查失败: ' . $e->getMessage());
+            return 1;
+        }
+
+        return 0;
+    }
+}

+ 2 - 1
app/Console/Kernel.php

@@ -22,7 +22,8 @@ class Kernel extends ConsoleKernel
      */
     protected function schedule(Schedule $schedule)
     {
-
+        // 每5分钟检查一次图片生成任务状态
+        $schedule->command('image-generation:check-tasks')->everyFiveMinutes();
     }
 
     /**

+ 112 - 0
app/Http/Controllers/ImageGenerationController.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Routing\Controller as BaseController;
+use App\Libs\Utils;
+use App\Services\AiImageGenerationService;
+use App\Transformer\ImageGenerationTransformer;
+use Illuminate\Http\Request;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Validator;
+use App\Libs\ApiResponse;
+
+class ImageGenerationController extends BaseController
+{
+    use ApiResponse;
+    private $aiImageGenerationService;
+
+    public function __construct(AiImageGenerationService $aiImageGenerationService)
+    {
+        $this->aiImageGenerationService = $aiImageGenerationService;
+    }
+
+    /**
+     * 创建图片生成任务
+     *
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function createTask(Request $request): JsonResponse
+    {
+        $data = $request->all();
+        $validator = Validator::make($data, [
+            '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',
+        ], [
+            '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之间',
+        ]);
+        if ($validator->fails()) {
+            Utils::throwError('1002:'.$validator->errors()->first());
+        }
+
+        try {
+            $task = $this->aiImageGenerationService->createImageGenerationTask($data);
+
+            return $this->success(['task_id' => $task->task_id, 'status' => $task->status]);
+        } catch (\Exception $e) {
+            Utils::throwError('1001:'.$e->getMessage());
+        }
+    }
+
+    /**
+     * 查询任务状态
+     *
+     * @param string $taskId
+     * @return JsonResponse
+     */
+    public function taskStatus(string $taskId): JsonResponse
+    {
+        $task = \App\Models\MpGeneratePicTask::where('task_id', $taskId)->first();
+
+        if (!$task) {
+            return response()->json([
+                'success' => false,
+                'message' => '任务不存在'
+            ], 404);
+        }
+
+        return response()->json([
+            'success' => true,
+            'data' => [
+                'task_id' => $task->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 ImageGenerationTransformer(), 'newBuildTaskList']);
+    }
+}

+ 34 - 0
app/Libs/Helpers.php

@@ -330,6 +330,40 @@ function uploadStreamByTos($prefix, $stream, $filename)
     return '';
 }
 
+// 从图片内容获取图片扩展名
+function getExtFromContent(string $url): ?string {
+    $imageInfo = @getimagesize($url);
+    if ($imageInfo !== false) {
+        // 返回MIME类型
+        return mimeToExt($imageInfo['mime']);
+        // 或者返回文件扩展名
+        // return image_type_to_extension($imageInfo[2], false);
+    }
+
+    return null;
+}
+
+// 辅助 MIME 映射函数
+function mimeToExt(string $mime): ?string {
+    $map = [
+        'audio/wav'                    => '.wav',
+        'audio/x-ms-wma'               => '.wma',
+        'video/x-ms-wmv'               => '.wmv',
+        'video/mp4'                    => '.mp4',
+        'audio/mpeg'                   => '.mp3',
+        'audio/amr'                    => '.amr',
+        'application/vnd.rn-realmedia' => '.rm',
+        'audio/mid'                    => '.mid',
+        'image/bmp'                    => '.bmp',
+        'image/gif'                    => '.gif',
+        'image/png'                    => '.png',
+        'image/tiff'                   => '.tiff',
+        'image/jpeg'                   => '.jpg',
+        'application/pdf'              => '.pdf',
+    ];
+    return $map[$mime] ?? null;
+}
+
 
 /**
  * 上传文件(阿里云oss)

+ 81 - 0
app/Models/MpGeneratePicTask.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class MpGeneratePicTask extends Model
+{
+    use HasFactory;
+
+    protected $fillable = [
+        'task_id',
+        'prompt',
+        'width',
+        'height',
+        'image_num',
+        'scale',
+        'ref_img_url',
+        'mask_img_url',
+        'extra_params',
+        'status',
+        'result_url',
+        'error_message',
+        'started_at',
+        'completed_at',
+        'created_at',
+        'updated_at',
+    ];
+
+    protected $casts = [
+        'result_url' => 'array',
+        'ref_img_url' => 'array',
+        'mask_img_url' => 'array',
+        'extra_params' => 'array',
+        'started_at' => 'datetime',
+        'completed_at' => 'datetime',
+    ];
+
+    const STATUS_PENDING = 'pending';
+    const STATUS_PROCESSING = 'processing';
+    const STATUS_SUCCESS = 'success';
+    const STATUS_FAILED = 'failed';
+
+    /**
+     * 更新任务状态
+     *
+     * @param string $status
+     * @param array $data
+     * @return bool
+     */
+    public function updateStatus(string $status, array $data = [])
+    {
+        $updateData = ['status' => $status];
+        if (!$this->created_at) {
+            $updateData['created_at'] = now();
+        }
+        $updateData['updated_at'] = now();
+        if (isset($data['extra_params'])) $updateData['extra_params'] = $data['extra_params'];
+
+        if ($status === self::STATUS_PROCESSING) {
+            if (isset($data['task_id'])) $updateData['task_id'] = $data['task_id'];
+
+            if (!$this->started_at) $updateData['started_at'] = now();
+        }
+
+        if (in_array($status, [self::STATUS_SUCCESS, self::STATUS_FAILED])) {
+            $updateData['completed_at'] = now();
+
+            if (isset($data['result_url'])) {
+                $updateData['result_url'] = $data['result_url'];
+            }
+
+            if (isset($data['error_message'])) {
+                $updateData['error_message'] = $data['error_message'];
+            }
+        }
+
+        return $this->update($updateData);
+    }
+}

+ 5 - 0
app/Providers/AppServiceProvider.php

@@ -2,6 +2,8 @@
 
 namespace App\Providers;
 
+use App\Services\AiImageGenerationService;
+use App\Services\VolcEngineService;
 use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\ServiceProvider;
 
@@ -15,6 +17,9 @@ class AppServiceProvider extends ServiceProvider
     public function register()
     {
         //
+        $this->app->singleton(AiImageGenerationService::class, function ($app) {
+            return new AiImageGenerationService($app->make(VolcEngineService::class));
+        });
     }
 
     /**

+ 295 - 0
app/Services/AiImageGenerationService.php

@@ -0,0 +1,295 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\MpGeneratePicTask;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use Illuminate\Support\Facades\Log;
+
+class AiImageGenerationService
+{
+    private $volcEngineService;
+    private $httpClient;
+
+    public function __construct(VolcEngineService $volcEngineService)
+    {
+        $this->volcEngineService = $volcEngineService;
+        $this->httpClient = new Client([
+            'timeout' => 300,
+        ]);
+    }
+
+    /**
+     * 异步创建图片生成任务
+     *
+     * @param array $params
+     * @return MpGeneratePicTask
+     */
+    public function createImageGenerationTask(array $params): MpGeneratePicTask
+    {
+        // 生成唯一的任务ID
+        $taskId = 'img_gen_' . time() . '_' . uniqid();
+
+        // 创建任务记录
+        $task = MpGeneratePicTask::create([
+            'task_id' => $taskId,
+            'prompt' => $params['prompt'] ?? '',
+            'width' => $params['width'] ?? 2048,
+            'height' => $params['height'] ?? 2048,
+            'image_num' => $params['image_num'] ?? 1,
+            'scale' => $params['scale'] ?? 50,
+            'ref_img_url' => $params['ref_img_url'] ?? null,
+            'mask_img_url' => $params['mask_img_url'] ?? null,
+            'extra_params' => $params['extra_params'] ?? null,
+            'status' => MpGeneratePicTask::STATUS_PENDING,
+        ]);
+
+        // 发送异步请求到即梦AI API创建任务
+        $this->submitTaskToApi($task);
+
+        return $task;
+    }
+
+    /**
+     * 提交任务到即梦AI API
+     *
+     * @param MpGeneratePicTask $task
+     * @return void
+     */
+    private function submitTaskToApi(MpGeneratePicTask $task): void
+    {
+        try {
+            // 验证环境变量配置
+            $accessKey = env('VOLC_AK');
+            $secretKey = env('VOLC_SK');
+                
+            if (empty($accessKey) || empty($secretKey)) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '火山引擎访问密钥未配置,请检查环境变量VOLC_AK和VOLC_SK'
+                ]);
+                return;
+            }
+                
+            //构建即梦AI 4.0 API请求参数
+            $apiParams = [
+                'req_key' => 'jimeng_t2i_v40',
+                'prompt' => $task->prompt,
+                'scale' => $task->scale / 100, //为0-1范围
+            ];
+            
+            // 添加可选参数
+            if ($task->image_num > 1) {
+                $apiParams['force_single'] = false;
+            } else {
+                $apiParams['force_single'] = true;
+            }
+            
+            // 参考图片
+            if ($task->ref_img_url) {
+                $apiParams['image_urls'] = [$task->ref_img_url];
+            }
+                        
+            if ($task->mask_img_url) {
+                $apiParams['image_urls'][] = $task->mask_img_url;
+            }
+            
+            // 尺寸参数
+            $area = $task->width * $task->height; //计算面积
+            if ($area >= 1024*1024 && $area <= 4096*4096) {
+                $apiParams['size'] = $area;
+            }
+            
+            //调用即梦AI 4.0 API提交任务
+            $response = $this->volcEngineService->request(
+                'POST',
+                'visual.volcengineapi.com', //即梦AI API域名
+                '/', // 使用根路径
+                [], // Query参数
+                json_encode($apiParams),
+                $accessKey,
+                $secretKey,
+                'cv', // 服务标识
+                'cn-north-1', //区域
+                'CVSync2AsyncSubmitTask',
+                '2022-08-31',
+                'application/json'
+            );
+    
+            $responseData = json_decode($response['body'], true);
+            dLog('generate')->info('即梦AI 4.0 API提交任务响应: ', $responseData);
+                
+            if ($responseData['code'] !== 10000) {
+                // API返回错误
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => $responseData['message'] ?? 'API Error',
+                    'extra_params' => $responseData['data'] ?? []
+                ]);
+            } else {
+                // 任务提交成功,更新状态为处理中
+                $updateData = [];
+                //存储API返回的任务ID
+                if (isset($responseData['data']['task_id'])) {
+                    $updateData['task_id'] = $responseData['data']['task_id'];
+                    $updateData['extra_params'] = $responseData['data'] ?? [];
+                }
+    
+                $task->updateStatus(MpGeneratePicTask::STATUS_PROCESSING, $updateData);
+            }
+        } catch (\Exception $e) {
+            // 记录错误
+            $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                'error_message' => 'API请求失败: ' . $e->getMessage()
+            ]);
+        }
+    }
+
+    public function getTaskList($data)
+    {
+        $task_id = getProp($data, 'task_id');
+        $status = getProp($data, 'status');
+
+        $query = MpGeneratePicTask::select('*');
+        if ($task_id) {
+            $query->where('task_id', $task_id);
+        }
+        if ($status) {
+            $query->where('status', $status);
+        }
+
+        return $query->orderBy('created_at', 'desc')->paginate();
+    }
+
+    /**
+     * 查询任务状态
+     *
+     * @param MpGeneratePicTask $task
+     * @return array
+     */
+    public function queryTaskStatus(MpGeneratePicTask $task): array
+    {
+        try {
+            // 获取API任务ID
+            $apiTaskId = $task->task_id ?? null;
+                
+            if (!$apiTaskId) {
+                return [
+                    'status' => 'failed',
+                    'error_message' => 'API任务ID不存在',
+                    'result_url' => null
+                ];
+            }
+    
+            //构建查询参数
+            $apiParams = [
+                'req_key' => 'jimeng_t2i_v40',
+                'task_id' => $apiTaskId,
+                'req_json' => '{"return_url":true}' // 返回URL格式
+            ];
+    
+            //调用即梦AI 4.0 API查询任务状态
+            $response = $this->volcEngineService->request(
+                'POST',
+                'visual.volcengineapi.com',
+                '/', // 使用根路径
+                [], // Query参数
+                json_encode($apiParams),
+                env('VOLC_AK'),
+                env('VOLC_SK'),
+                'cv',
+                'cn-north-1',
+                'CVSync2AsyncGetResult',
+                '2022-08-31',
+                'application/json'
+            );
+    
+            $responseData = json_decode($response['body'], true);
+            dLog('generate')->info('即梦AI 4.0 API查询任务状态响应: ', $responseData);
+    
+            if ($responseData['code'] !== 10000) {
+                // API返回错误
+                return [
+                    'status' => 'failed',
+                    'error_message' => $responseData['message'] ?? 'API Error',
+                    'result_url' => null
+                ];
+            }
+    
+            // 解析API响应
+            $result = $responseData['data'] ?? [];
+            $taskStatus = $result['status'] ?? 'failed';
+    
+            $returnData = [
+                'status' => $taskStatus,
+                'error_message' => null,
+                'result_url' => null
+            ];
+    
+            // 如果任务成功,获取结果URL
+            if ($taskStatus === 'done' && isset($result['image_urls']) && count($result['image_urls']) > 0) {
+                $result_urls = [];
+                foreach ($result['image_urls'] as $url) {
+                    $pic_name = 'ai_generation_' . time() . '_' . uniqid();
+                    $pic_ext = getExtFromContent($url);
+                    $pic_name = $pic_name . '.' . $pic_ext;
+                    
+                    // 将图片另存到tos
+                    // $url = uploadStreamByTos('img_generation', file_get_contents($url), $pic_name);
+                    $result_urls[] = $url;  
+                }
+                $returnData['result_url'] = $result_urls;
+                $returnData['status'] = 'success';
+            } elseif (in_array($taskStatus, ['not_found', 'expired'])) {
+                $returnData['status'] = 'failed';
+                $returnData['error_message'] = '任务未找到或已过期';
+            } elseif ($taskStatus === 'failed') {
+                $returnData['status'] = 'failed';
+                $returnData['error_message'] = '任务执行失败';
+            }
+            // in_queue 或 generating状态保持原status
+    
+            return $returnData;
+        } catch (\Exception $e) {
+            return [
+                'status' => 'failed',
+                'error_message' => $e->getMessage(),
+                'result_url' => null
+            ];
+        }
+    }
+
+    /**
+     * 更新所有待处理任务的状态
+     *
+     * @return void
+     */
+    public function updatePendingTasks(): void
+    {
+        // 获取所有处理中的任务
+        $tasks = MpGeneratePicTask::where('status', MpGeneratePicTask::STATUS_PROCESSING)
+            ->get();
+
+        foreach ($tasks as $task) {
+            $statusInfo = $this->queryTaskStatus($task);
+
+            if ($statusInfo['status'] === 'success') {
+                $task->updateStatus(MpGeneratePicTask::STATUS_SUCCESS, [
+                    'result_url' => $statusInfo['result_url']
+                ]);
+            } elseif ($statusInfo['status'] === 'failed') {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => $statusInfo['error_message']
+                ]);
+            }
+            // 如果仍然是处理中状态,不做任何操作
+            
+            //处理:如果任务处理超过12小时,标记为失败
+            $processingTime = now()->diffInHours($task->created_at);
+            if ($processingTime > 12) {
+                $task->updateStatus(MpGeneratePicTask::STATUS_FAILED, [
+                    'error_message' => '任务处理超时(超过12小时)'
+                ]);
+            }
+        }
+    }
+}

+ 84 - 107
app/Services/VolcEngineService.php

@@ -13,7 +13,7 @@ class VolcEngineService
     public function __construct()
     {
         $this->client = new Client([
-            'timeout' => 300,
+            'timeout' => 120.0,
         ]);
     }
 
@@ -54,128 +54,105 @@ class VolcEngineService
         string $version,
         string $contentType = 'application/json'
     ): array {
-        $signed = $this->sign(
-            $method,
-            $host,
-            $path,
-            $query,
-            $body,
-            $ak,
-            $sk,
-            $service,
-            $region,
-            $action,
-            $version,
-            $contentType
-        );
-
-        $response = $this->client->request($method, 'https://' . $host . $path, [
-            'headers' => $signed['headers'],
-            'query'   => $signed['query'],
-            'body'    => $body,
-        ]);
-
-        return [
-            'status'  => $response->getStatusCode(),
-            'headers' => $response->getHeaders(),
-            'body'    => (string) $response->getBody(),
+        // 初始化身份证明结构体
+        $credential = [
+            'accessKeyId' => $ak,
+            'secretKeyId' => $sk,
+            'service' => $service,
+            'region' => $region,
         ];
-    }
-
-    /**
-     * 生成火山引擎签名头部和 query
-     *
-     * @return array [
-     *     'headers' => [...],   // 需要加到 HTTP Header 的字段
-     *     'query'   => [...],   // 已合并 Action/Version 并排序后的 query
-     * ]
-     */
-    public function sign(
-        string $method,
-        string $host,
-        string $path,
-        array  $query,
-        string $body,
-        string $ak,
-        string $sk,
-        string $service,
-        string $region,
-        string $action,
-        string $version,
-        string $contentType = 'application/json'
-    ): array {
-        // 1. 合并公共参数,并按 key 排序
+        
+        // 合并公共参数,并按 key 排序
         $query = array_merge($query, [
-            'Action'  => $action,
-            'Version' => $version,
+            'Action' => $action,
+            'Version' => $version
         ]);
         ksort($query);
 
-        // 2. 时间与 Body 哈希
-        $xDate     = gmdate('Ymd\THis\Z');     // 例如 20250105T081030Z
-        $shortDate = substr($xDate, 0, 8);       // 例如 20250105
-        $xContentSha256 = hash('sha256', $body);
-
-        // 3. CanonicalRequest
-        $signedHeaders = 'content-type;host;x-content-sha256;x-date';
-
-        $canonicalHeaders = implode("\n", [
-            'content-type:' . $contentType,
-            'host:' . $host,
-            'x-content-sha256:' . $xContentSha256,
-            'x-date:' . $xDate,
-        ]);
+        // 初始化请求参数
+        $requestParam = [
+            'body' => $body,
+            'host' => $host,
+            'path' => $path,
+            'method' => $method,
+            'contentType' => $contentType,
+            'date' => gmdate('Ymd\\THis\\Z'),
+            'query' => $query
+        ];
 
-        $canonicalQueryString = http_build_query($query);
+        // 计算签名
+        $xDate = $requestParam['date'];
+        $shortXDate = substr($xDate, 0, 8);
+        $xContentSha256 = hash('sha256', $requestParam['body']);
+        
+        $signResult = [
+            'Host' => $requestParam['host'],
+            'X-Content-Sha256' => $xContentSha256,
+            'X-Date' => $xDate,
+            'Content-Type' => $requestParam['contentType']
+        ];
 
-        $canonicalRequest = implode("\n", [
-            strtoupper($method),
-            $path,
-            $canonicalQueryString,
-            $canonicalHeaders,
+        // 计算 Signature 签名
+        $signedHeaderStr = join(';', ['content-type', 'host', 'x-content-sha256', 'x-date']);
+        $canonicalRequestStr = join("\n", [
+            $requestParam['method'],
+            $requestParam['path'],
+            http_build_query($requestParam['query']),
+            join("\n", [
+                'content-type:'. $requestParam['contentType'], 
+                'host:'. $requestParam['host'], 
+                'x-content-sha256:'. $xContentSha256, 
+                'x-date:'. $xDate
+            ]),
             '',
-            $signedHeaders,
-            $xContentSha256,
+            $signedHeaderStr,
+            $xContentSha256
         ]);
 
-        $hashedCanonicalRequest = hash('sha256', $canonicalRequest);
-
-        // 4. StringToSign
-        $credentialScope = implode('/', [$shortDate, $region, $service, 'request']);
-        $stringToSign = implode("\n", [
-            'HMAC-SHA256',
-            $xDate,
-            $credentialScope,
-            $hashedCanonicalRequest,
+        $hashedCanonicalRequest = hash("sha256", $canonicalRequestStr);
+        $credentialScope = join('/', [$shortXDate, $credential['region'], $credential['service'], 'request']);
+        $stringToSign = join("\n", [
+            'HMAC-SHA256', 
+            $xDate, 
+            $credentialScope, 
+            $hashedCanonicalRequest
         ]);
-
-        // 5. 派生签名 key
-        $kDate    = hash_hmac('sha256', $shortDate, $sk, true);
-        $kRegion  = hash_hmac('sha256', $region, $kDate, true);
-        $kService = hash_hmac('sha256', $service, $kRegion, true);
-        $kSigning = hash_hmac('sha256', 'request', $kService, true);
-
-        // 6. 计算最终签名
-        $signature = hash_hmac('sha256', $stringToSign, $kSigning);
-
-        $authorization = sprintf(
-            'HMAC-SHA256 Credential=%s, SignedHeaders=%s, Signature=%s',
-            $ak . '/' . $credentialScope,
-            $signedHeaders,
+        
+        $kDate = hash_hmac("sha256", $shortXDate, $credential['secretKeyId'], true);
+        $kRegion = hash_hmac("sha256", $credential['region'], $kDate, true);
+        $kService = hash_hmac("sha256", $credential['service'], $kRegion, true);
+        $kSigning = hash_hmac("sha256", 'request', $kService, true);
+        $signature = hash_hmac("sha256", $stringToSign, $kSigning);
+        
+        $signResult['Authorization'] = sprintf(
+            "HMAC-SHA256 Credential=%s, SignedHeaders=%s, Signature=%s", 
+            $credential['accessKeyId']. '/'. $credentialScope, 
+            $signedHeaderStr, 
             $signature
         );
 
-        $headers = [
-            'Host'             => $host,
-            'Content-Type'     => $contentType,
-            'X-Content-Sha256' => $xContentSha256,
-            'X-Date'           => $xDate,
-            'Authorization'    => $authorization,
-        ];
+        $header = array_merge([], $signResult); // 空的$header数组
+
+        // 发送 HTTP 请求
+        $client = new Client([
+            'base_uri' => 'https://'. $requestParam['host'],
+            'timeout' => 120.0,
+        ]);
+        
+        $response = $client->request($method, $requestParam['path'], [
+            'headers' => $header,
+            'query' => $requestParam['query'],
+            'body' => $requestParam['body']
+        ]);
+
+        $responseContent = $response->getBody()->getContents();
+        // 转换 \u0026 为 &
+        $responseContent = str_replace('\u0026', '&', $responseContent);
 
         return [
-            'headers' => $headers,
-            'query'   => $query,
+            'status'  => $response->getStatusCode(),
+            'headers' => $response->getHeaders(),
+            'body'    => $responseContent,
         ];
     }
 }

+ 54 - 0
app/Transformer/ImageGenerationTransformer.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Transformer;
+
+use Illuminate\Support\Facades\DB;
+
+class ImageGenerationTransformer
+{
+    // 剧本列表
+    public function newBuildTaskList($data): array
+    {
+        return [
+            'meta'      => getMeta($data),
+            // 'header'    => $data['header'],
+            'list'      => $this->newEachTaskList($data),
+        ];
+    }
+
+    private function newEachTaskList($list): array
+    {
+        $result = [];
+        if (empty($list)) return $result;
+
+        foreach ($list as $item) {
+            $status = getProp($item, 'status');
+            $status_arr =[
+                'pending' => '待生成',
+                'processing' => '生成中',
+                'success' => '生成成功',
+                'failed' => '生成失败',
+            ];
+
+            $result[] = [
+                'task_id'               => getProp($item, 'task_id'),
+                'prompt'                => getProp($item, 'prompt'),
+                'width'                 => getProp($item, 'width'),
+                'height'                => getProp($item, 'height'),
+                'image_num'             => getProp($item, 'image_num'),
+                'scale'                 => getProp($item, 'scale'),
+                'ref_img_url'           => getProp($item, 'ref_img_url'),
+                'result_url'            => getProp($item, 'result_url'),
+                'status'                => $status,
+                'status_info'           => isset($status_arr[$status]) ? $status_arr[$status] : '',
+                'created_at'            => transDate(getProp($item, 'created_at')),
+                'updated_at'            => transDate(getProp($item, 'updated_at')),
+                'extra_params'          => getProp($item, 'extra_params'),
+            ];
+        }
+
+        return $result;
+    }
+
+    
+}

+ 8 - 0
routes/api.php

@@ -4,6 +4,7 @@ use App\Http\Controllers\Account\AccountController;
 use App\Http\Controllers\DeepSeek\DeepSeekController;
 use App\Http\Controllers\Book\BookController;
 use App\Http\Controllers\Timbre\TimbreController;
+use App\Http\Controllers\ImageGenerationController;
 use Illuminate\Support\Facades\Route;
 
 /*
@@ -90,6 +91,13 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
         Route::post('chatWithFile', [DeepSeekController::class, 'chatWithFile']);
         Route::post('generateScript', [DeepSeekController::class, 'generateScript']);
     });
+
+    // 图片生成相关路由
+    Route::group(['prefix' => 'imageGeneration'], function () {
+        Route::post('createTask', [ImageGenerationController::class, 'createTask']);
+        Route::get('taskStatus', [ImageGenerationController::class, 'taskStatus']);
+        Route::get('taskList', [ImageGenerationController::class, 'taskList']);
+    });
     
 });
 Route::get('login', [AccountController::class, 'login']); // 登录