|
|
@@ -4,6 +4,7 @@ use app\service\AIGatewayService;
|
|
|
use think\Db;
|
|
|
use think\Queue;
|
|
|
use think\queue\Job;
|
|
|
+
|
|
|
/**
|
|
|
* 文生图任务处理类
|
|
|
* 描述:接收提示词,通过模型生成图像,保存图像并记录数据库信息,是链式任务中的最后一环
|
|
|
@@ -17,7 +18,6 @@ class TextToImageJob
|
|
|
*/
|
|
|
public function fire(Job $job, $data)
|
|
|
{
|
|
|
-
|
|
|
$logId = $data['log_id'] ?? null;
|
|
|
|
|
|
try {
|
|
|
@@ -40,7 +40,7 @@ class TextToImageJob
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
- //拼接原图文件路径 + 图片名称
|
|
|
+ // 拼接原图路径
|
|
|
$old_image_url = rtrim($data['sourceDir'], '/') . '/' . ltrim($data['file_name'], '/');
|
|
|
|
|
|
$list = Db::name("text_to_image")
|
|
|
@@ -49,7 +49,6 @@ class TextToImageJob
|
|
|
->where('status', 0)
|
|
|
->select();
|
|
|
|
|
|
-
|
|
|
if (!empty($list)) {
|
|
|
$total = count($list);
|
|
|
echo "📊 共需处理:{$total} 条记录\n";
|
|
|
@@ -59,7 +58,7 @@ class TextToImageJob
|
|
|
$begin = date('Y-m-d H:i:s');
|
|
|
echo "👉 正在处理第 {$currentIndex} 条,ID: {$row['id']}\n";
|
|
|
|
|
|
- // 调用生成图像方法
|
|
|
+ // 图像生成
|
|
|
$result = $this->textToImage(
|
|
|
$data["file_name"],
|
|
|
$data["outputDir"],
|
|
|
@@ -70,38 +69,44 @@ class TextToImageJob
|
|
|
$data["selectedOption"]
|
|
|
);
|
|
|
|
|
|
- $resultText = ($result === true || $result === 1 || $result === '成功') ? '成功' : '失败或无返回';
|
|
|
- echo "✅ 处理结果:{$resultText}\n";
|
|
|
+ // 标准化结果文本
|
|
|
+ if ($result === true || $result === 1 || $result === '成功') {
|
|
|
+ $resultText = '成功';
|
|
|
+ } else {
|
|
|
+ $resultText = (string) $result ?: '失败或无返回';
|
|
|
+ }
|
|
|
|
|
|
- $end = date('Y-m-d H:i:s');
|
|
|
- echo "完成时间:{$end}\n";
|
|
|
+ echo "✅ 处理结果:{$resultText}\n";
|
|
|
+ echo "完成时间:" . date('Y-m-d H:i:s') . "\n";
|
|
|
echo "Processed: " . static::class . "\n";
|
|
|
echo "文生图已处理完成\n\n";
|
|
|
- }
|
|
|
|
|
|
- // 更新日志状态:成功
|
|
|
- if ($logId) {
|
|
|
- Db::name('image_task_log')->where('id', $logId)->update([
|
|
|
- 'status' => 2,
|
|
|
- 'log' => '文生图处理成功',
|
|
|
- 'update_time' => date('Y-m-d H:i:s')
|
|
|
- ]);
|
|
|
+ // 若包含关键词,日志状态标为失败(-1)
|
|
|
+ if (strpos($resultText, '包含关键词') !== false) {
|
|
|
+ // 命中关键词类错误,状态设为失败
|
|
|
+ if ($logId) {
|
|
|
+ Db::name('image_task_log')->where('id', $logId)->update([
|
|
|
+ 'status' => -1,
|
|
|
+ 'log' => $resultText,
|
|
|
+ 'update_time' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ // 日志状态设置为成功(仅在未提前失败时)
|
|
|
+ if ($logId) {
|
|
|
+ Db::name('image_task_log')->where('id', $logId)->update([
|
|
|
+ 'status' => 2,
|
|
|
+ 'log' => '文生图处理成功',
|
|
|
+ 'update_time' => date('Y-m-d H:i:s')
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ continue; //跳过当前记录的后续处理
|
|
|
}
|
|
|
-
|
|
|
echo date('Y-m-d H:i:s') . " ✅ 文生图任务全部完成\n";
|
|
|
- } else {
|
|
|
- echo "⚠ 未找到可处理的数据,跳过执行\n";
|
|
|
-
|
|
|
- if ($logId) {
|
|
|
- Db::name('image_task_log')->where('id', $logId)->update([
|
|
|
- 'status' => 2,
|
|
|
- 'log' => '无数据可处理,已跳过'.$old_image_url,
|
|
|
- 'update_time' => date('Y-m-d H:i:s')
|
|
|
- ]);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
- // 如果还有链式任务,继续推送
|
|
|
+ // 处理链式任务(如果有)
|
|
|
if (!empty($data['chain_next'])) {
|
|
|
$nextType = array_shift($data['chain_next']);
|
|
|
$data['type'] = $nextType;
|
|
|
@@ -175,7 +180,6 @@ class TextToImageJob
|
|
|
// 调用文生图模型接口生成图像
|
|
|
$startTime = microtime(true);
|
|
|
|
|
|
-
|
|
|
// 清理 prompt 的换行
|
|
|
$prompt = preg_replace('/[\r\n\t]+/', ' ', $prompt);
|
|
|
|
|
|
@@ -185,106 +189,116 @@ class TextToImageJob
|
|
|
// 判断提示词中是否包含关键词(不区分大小写)
|
|
|
if (stripos($prompt, $keyword) !== false) {
|
|
|
$skipId = $record['id'];
|
|
|
- echo "🚫 跳过生成:提示词中包含关键词“{$keyword}”,记录 ID:{$skipId}\n";
|
|
|
- $updateRes = Db::name('text_to_image')->where('id', $skipId)->update([
|
|
|
+ echo "🚫 跳过生成:包含关键词“{$keyword}”,记录 ID:{$skipId}\n";
|
|
|
+ Db::name('text_to_image')->where('id', $skipId)->update([
|
|
|
'status' => 3,
|
|
|
- 'error_msg' => "提示词中包含关键词".$keyword,
|
|
|
+ 'error_msg' => "包含关键词".$keyword,
|
|
|
'update_time' => date('Y-m-d H:i:s')
|
|
|
]);
|
|
|
- return "跳过生成:记录 ID {$skipId},包含关键词 - {$keyword}";
|
|
|
+ return "包含关键词 - {$keyword}";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//文生图调用
|
|
|
+ $startTime = microtime(true);
|
|
|
+
|
|
|
$ai = new AIGatewayService();
|
|
|
$dalle1024 = $ai->callDalleApi($prompt,$selectedOption);
|
|
|
|
|
|
- //检测查询接口调用时长
|
|
|
$endTime = microtime(true);
|
|
|
$executionTime = $endTime - $startTime;
|
|
|
- echo "API调用耗时: " . round($executionTime, 3) . " 秒\n";
|
|
|
-
|
|
|
- // 检查 URL 返回是否成功
|
|
|
- if (!isset($dalle1024['data'][0]['url']) || empty($dalle1024['data'][0]['url'])) {
|
|
|
- $errorText = $dalle1024['error']['message'] ?? '未知错误';
|
|
|
- Db::name('text_to_image')->where('id', $record['id'])->update([
|
|
|
- 'error_msg' => '生成失败:' . $errorText,
|
|
|
- 'status' => 0
|
|
|
- ]);
|
|
|
- return '生成失败:' . $errorText;
|
|
|
+ echo "✅ API 调用耗时: " . round($executionTime, 3) . " 秒\n";
|
|
|
+
|
|
|
+ // 错误检查
|
|
|
+ if (isset($dalle1024['error'])) {
|
|
|
+ throw new \Exception("❌ 图像生成接口错误:" . ($dalle1024['error']['message'] ?? '未知错误'));
|
|
|
}
|
|
|
|
|
|
- // 下载图像
|
|
|
- $imgUrl1024 = $dalle1024['data'][0]['url'];
|
|
|
- $imgData1024 = @file_get_contents($imgUrl1024);
|
|
|
- if (!$imgData1024 || strlen($imgData1024) < 1000) {
|
|
|
- return "下载图像失败或内容异常";
|
|
|
+ // 提取 url 图像
|
|
|
+// $base64Image = $dalle1024['data'][0]['url'] ?? null;
|
|
|
+
|
|
|
+ // 提取 base64 图像
|
|
|
+ $base64Image = $dalle1024['data'][0]['b64_json'] ?? null;
|
|
|
+
|
|
|
+ if (!$base64Image || strlen($base64Image) < 1000) {
|
|
|
+ file_put_contents('/tmp/empty_image_base64.txt', json_encode($dalle1024, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
|
+ throw new \Exception("❌ 图像内容为空或异常,详情写入 /tmp/empty_image_base64.txt");
|
|
|
}
|
|
|
|
|
|
- // 保存原图(1024x1024)
|
|
|
- $img_name = preg_replace('/[^\x{4e00}-\x{9fa5}A-Za-z0-9_\- ]/u', '', $img_name);
|
|
|
- $img_name = mb_substr($img_name, 0, 30); // 限制为前30个字符(避免路径过长)
|
|
|
- $filename1024 = $img_name . '.png';
|
|
|
- $savePath1024 = $fullBaseDir . '1024x1024/' . $filename1024;
|
|
|
- file_put_contents($savePath1024, $imgData1024);
|
|
|
+ // 解码图片
|
|
|
+ $imgData = base64_decode($base64Image);
|
|
|
+
|
|
|
+ // 判断是否为图像
|
|
|
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
|
+ $mimeType = finfo_buffer($finfo, $imgData);
|
|
|
+ finfo_close($finfo);
|
|
|
+
|
|
|
+ // 保存图片路径
|
|
|
+ $img_name = mb_substr(preg_replace('/[^\x{4e00}-\x{9fa5}A-Za-z0-9_\- ]/u', '', $img_name), 0, 30);
|
|
|
+ $filename = $img_name . '.png';
|
|
|
+
|
|
|
+ $saveDir1024 = $fullBaseDir . '1024x1024/';
|
|
|
+ $saveDirCustom = $fullBaseDir . "{$width}x{$height}/";
|
|
|
+ @mkdir($saveDir1024, 0755, true);
|
|
|
+ @mkdir($saveDirCustom, 0755, true);
|
|
|
|
|
|
- // 图像裁剪生成自定义尺寸图
|
|
|
- $im = @imagecreatefromstring($imgData1024);
|
|
|
- if (!$im) return "图像损坏或格式不支持";
|
|
|
+ $path1024 = $saveDir1024 . $filename;
|
|
|
+ $pathCustom = $saveDirCustom . $filename;
|
|
|
|
|
|
- $srcWidth = imagesx($im);
|
|
|
- $srcHeight = imagesy($im);
|
|
|
- $srcRatio = $srcWidth / $srcHeight;
|
|
|
+ file_put_contents($path1024, $imgData);
|
|
|
+
|
|
|
+ // 解析图像内容
|
|
|
+ try {
|
|
|
+ $im = \imagecreatefromstring($imgData);
|
|
|
+ if (!$im) {
|
|
|
+ file_put_contents('/tmp/corrupted.png', $imgData);
|
|
|
+ throw new \Exception("❌ 图像无法解析,写入 /tmp/corrupted.png");
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ file_put_contents('/tmp/corrupted.png', $imgData);
|
|
|
+ throw new \Exception("❌ 图像处理异常:" . $e->getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 裁剪
|
|
|
+ $srcW = imagesx($im);
|
|
|
+ $srcH = imagesy($im);
|
|
|
+ $srcRatio = $srcW / $srcH;
|
|
|
$dstRatio = $width / $height;
|
|
|
|
|
|
- // 居中裁剪逻辑
|
|
|
if ($srcRatio > $dstRatio) {
|
|
|
- $cropHeight = $srcHeight;
|
|
|
- $cropWidth = intval($srcHeight * $dstRatio);
|
|
|
- $srcX = intval(($srcWidth - $cropWidth) / 2);
|
|
|
+ $cropW = intval($srcH * $dstRatio);
|
|
|
+ $cropH = $srcH;
|
|
|
+ $srcX = intval(($srcW - $cropW) / 2);
|
|
|
$srcY = 0;
|
|
|
} else {
|
|
|
- $cropWidth = $srcWidth;
|
|
|
- $cropHeight = intval($srcWidth / $dstRatio);
|
|
|
+ $cropW = $srcW;
|
|
|
+ $cropH = intval($srcW / $dstRatio);
|
|
|
$srcX = 0;
|
|
|
- $srcY = intval(($srcHeight - $cropHeight) / 2);
|
|
|
+ $srcY = intval(($srcH - $cropH) / 2);
|
|
|
}
|
|
|
|
|
|
$dstImg = imagecreatetruecolor($width, $height);
|
|
|
- imagecopyresampled($dstImg, $im, 0, 0, $srcX, $srcY, $width, $height, $cropWidth, $cropHeight);
|
|
|
+ imagecopyresampled($dstImg, $im, 0, 0, $srcX, $srcY, $width, $height, $cropW, $cropH);
|
|
|
|
|
|
- // 保存裁剪后图像
|
|
|
- $filenameCustom = $img_name . ".png";
|
|
|
- $savePathCustom = $fullBaseDir . "{$width}x{$height}/" . $filenameCustom;
|
|
|
- imagepng($dstImg, $savePathCustom);
|
|
|
+ // 保存裁剪图
|
|
|
+ imagepng($dstImg, $pathCustom);
|
|
|
imagedestroy($im);
|
|
|
imagedestroy($dstImg);
|
|
|
|
|
|
- // 成功写入数据库记录
|
|
|
- $status = trim($img_name) === '' ? 0 : 1;
|
|
|
-
|
|
|
- // 根据 selectedOption 设置 quality 和 style
|
|
|
- $quality = 'hd';
|
|
|
- $style = 'vivid';
|
|
|
- if ($selectedOption === 'dall-e-3') {
|
|
|
- $quality = 'standard';
|
|
|
- $style = 'vivid';
|
|
|
- }
|
|
|
-
|
|
|
- $updateRes = Db::name('text_to_image')->where('id', $record['id'])->update([
|
|
|
- 'new_image_url' => str_replace($rootPath . 'public/', '', $savePath1024),
|
|
|
- 'custom_image_url' => str_replace($rootPath . 'public/', '', $savePathCustom),
|
|
|
+ // 写入数据库
|
|
|
+ Db::name('text_to_image')->where('id', $record['id'])->update([
|
|
|
+ 'new_image_url' => str_replace($rootPath . 'public/', '', $path1024),
|
|
|
+ 'custom_image_url' => str_replace($rootPath . 'public/', '', $pathCustom),
|
|
|
'img_name' => $img_name,
|
|
|
'model' => $selectedOption,
|
|
|
- 'status' => $status,
|
|
|
+ 'status' => trim($img_name) === '' ? 0 : 1,
|
|
|
'status_name' => "文生图",
|
|
|
'size' => "{$width}x{$height}",
|
|
|
- 'quality' => $quality,
|
|
|
- 'style' => $style,
|
|
|
+ 'quality' => 'standard',
|
|
|
+ 'style' => 'vivid',
|
|
|
'error_msg' => '',
|
|
|
'update_time' => date('Y-m-d H:i:s')
|
|
|
]);
|
|
|
return "成功";
|
|
|
}
|
|
|
-
|
|
|
}
|