ImageToImageJob.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. <?php
  2. namespace app\job;
  3. use app\api\controller\Common;
  4. use app\service\AIGatewayService;
  5. use app\service\ImageService;
  6. use think\Db;
  7. use think\queue\Job;
  8. use think\Queue;
  9. //图生图
  10. class ImageToImageJob{
  11. public function fire(Job $job, $data)
  12. {
  13. //产品图+模板图) 时走此分支
  14. if (isset($data['status_val']) && $data['status_val'] == '图生图') {
  15. $taskId = $data['task_id'];
  16. // 幂等:若任务已完成则跳过,避免超时重试导致重复执行
  17. $redis = getTaskRedis();
  18. $existing = $redis->get("img_to_img_task:{$taskId}");
  19. if ($existing) {
  20. $info = json_decode($existing, true);
  21. if (isset($info['status']) && ImageService::isTaskCompleted($info['status'])) {
  22. echo "任务 {$taskId} 已完成,跳过重复执行\n";
  23. $job->delete();
  24. return;
  25. }
  26. }
  27. try {
  28. // 标记处理中(前端轮询可见)
  29. $redis->set("img_to_img_task:{$taskId}", json_encode([
  30. 'status' => ImageService::TASK_STATUS_PROCESSING,
  31. 'started_at' => date('Y-m-d H:i:s'),
  32. ], JSON_UNESCAPED_UNICODE), ['EX' => 300]);
  33. \think\Log::info("[ImageToImageJob] start task_id={$taskId} status_type=" . ($data['status_type'] ?? ''));
  34. $result = $this->get_img_to_img($data);
  35. // get_img_to_img 内部已写入 img_to_img_task,此处无需重复写入
  36. if (is_array($result) && isset($result['code']) && $result['code'] !== 0) {
  37. throw new \Exception($result['msg'] ?? '图生图失败');
  38. }
  39. if (is_array($result) && !empty($result['success_report'])) {
  40. echo $result['success_report'] . "\n";
  41. } else {
  42. $imgPath = is_array($result) ? ($result['path'] ?? '') : $result;
  43. echo "🎉 任务 {$taskId} 执行完成,图片生成成功!\n";
  44. if ($imgPath !== '') {
  45. echo "生成图片:{$imgPath}\n";
  46. }
  47. }
  48. echo "结束时间:" . date('Y-m-d H:i:s') . "\n";
  49. $job->delete();
  50. } catch (\Exception $e) {
  51. echo $e->getMessage() . "\n";
  52. \think\Log::error('[ImageToImageJob] ' . $e->getMessage());
  53. $job->delete();
  54. }
  55. } else {
  56. $logId = $data['log_id'] ?? null;
  57. try {
  58. // 任务类型校验(必须是图生图)
  59. if (!isset($data['type']) || $data['type'] !== '图生图') {
  60. $job->delete();
  61. return;
  62. }
  63. $startTime = date('Y-m-d H:i:s');
  64. echo "━━━━━━━━━━ ▶ 图生图任务开始处理━━━━━━━━━━\n";
  65. echo "处理时间:{$startTime}\n";
  66. // 更新日志状态:处理中
  67. if ($logId) {
  68. Db::name('image_task_log')->where('id', $logId)->update([
  69. 'status' => 1,
  70. 'log' => '图生图处理中',
  71. 'update_time' => $startTime
  72. ]);
  73. }
  74. //拼接原图文件路径 + 图片名称
  75. $old_image_url = rtrim($data['sourceDir'], '/') . '/' . ltrim($data['file_name'], '/');
  76. $list = Db::name("text_to_image")
  77. ->where('old_image_url', $old_image_url)
  78. ->where('img_name', '<>', '')
  79. // ->where('status', 1)
  80. ->select();
  81. if (!empty($list)) {
  82. $total = count($list);
  83. echo "📊 共需处理:{$total} 条记录\n\n";
  84. foreach ($list as $index => $row) {
  85. $currentIndex = $index + 1;
  86. $begin = date('Y-m-d H:i:s');
  87. echo "处理时间:{$begin}\n";
  88. echo "👉 正在处理第 {$currentIndex} 条,ID: {$row['id']}\n";
  89. // 调用生成图像方法
  90. $result = $this->ImageToImage(
  91. $data["file_name"],
  92. $data["outputDir"],
  93. $row["new_image_url"],
  94. $row["img_name"],
  95. 1024,
  96. 1303
  97. );
  98. $resultText = ($result === true || $result === 1 || $result === '成功') ? '成功' : '失败或无返回';
  99. echo "✅ 处理结果:{$resultText}\n";
  100. $end = date('Y-m-d H:i:s');
  101. echo "完成时间:{$end}\n";
  102. echo "Processed: " . static::class . "\n";
  103. echo "图生图已处理完成\n\n";
  104. }
  105. // 更新日志状态:成功
  106. if ($logId) {
  107. Db::name('image_task_log')->where('id', $logId)->update([
  108. 'status' => 2,
  109. 'log' => '图生图处理成功',
  110. 'update_time' => date('Y-m-d H:i:s')
  111. ]);
  112. }
  113. echo date('Y-m-d H:i:s') . " 图生图任务全部完成\n";
  114. } else {
  115. echo "未找到可处理的数据,跳过执行\n";
  116. if ($logId) {
  117. Db::name('image_task_log')->where('id', $logId)->update([
  118. 'status' => 2,
  119. 'log' => '无数据可处理,已跳过'.$old_image_url,
  120. 'update_time' => date('Y-m-d H:i:s')
  121. ]);
  122. }
  123. }
  124. // 如果还有链式任务,继续推送
  125. if (!empty($data['chain_next'])) {
  126. $nextType = array_shift($data['chain_next']);
  127. $data['type'] = $nextType;
  128. Queue::push('app\job\ImageArrJob', [
  129. 'task_id' => $data['task_id'],
  130. 'data' => [$data]
  131. ], 'arrimage');
  132. }
  133. $job->delete();
  134. } catch (\Exception $e) {
  135. //异常处理,记录失败日志
  136. echo "错误信息: " . $e->getMessage() . "\n";
  137. echo "文件: " . $e->getFile() . "\n";
  138. echo "行号: " . $e->getLine() . "\n";
  139. // 删除当前任务
  140. $job->delete();
  141. }
  142. }
  143. }
  144. /**
  145. * 失败回调(可用于后续通知或重试机制)
  146. */
  147. public function failed($data)
  148. {
  149. echo "ImageJob failed: " . json_encode($data);
  150. }
  151. /**
  152. * 图生图主处理方法:根据产品图+参考图生成新图
  153. *
  154. * 业务分支:
  155. * - ProductImageGeneration:产品图创作(前端传 base64,先调模型成功后再落盘入库)
  156. * - ProductTemplateReplace:产品替换(读取已有产品图/模板图路径,生成效果图并返回前端展示)
  157. * - ProductWhiteBackground:产品白底抠图(单张 product_img,结果写入 product_white_background 表)
  158. *
  159. * 入参说明($data):
  160. * - prompt: 提示词
  161. * - size: 尺寸(如 1:1、9:16、768x1024)
  162. * - status_val: 任务类型(图生图)
  163. * - status_type: 页面/流程类型(决定走哪个业务分支)
  164. * - product_img: 产品图(base64 或相对路径)
  165. * - template_img: 参考图(base64 或相对路径)
  166. * - model: 模型名称
  167. * - sys_id: 操作用户
  168. * - task_id: 可选,异步任务ID(用于写 Redis 状态)
  169. *
  170. * @param array $data 图生图任务参数
  171. * @return string|array 成功返回生成图相对路径;失败返回 ['code'=>1,'msg'=>错误信息]
  172. */
  173. public function get_img_to_img($data)
  174. {
  175. //接收参数
  176. $prompt = trim($data['prompt'] ?? '');
  177. $size = trim($data['size'] ?? '');
  178. $statusVal = trim($data['status_val'] ?? '');
  179. $statusType = trim($data['status_type'] ?? '');
  180. $productImg = trim($data['product_img'] ?? '');
  181. $templateImg = trim($data['template_img'] ?? '');
  182. $oldImg = trim($data['old_img'] ?? '');
  183. $refImg = trim($data['ref_img'] ?? '');
  184. $height = trim((string)($data['height'] ?? ''));
  185. $width = trim((string)($data['width'] ?? ''));
  186. $model = trim($data['model'] ?? '');
  187. $sysId = $data['sys_id'];
  188. $sysrq = date('Y-m-d H:i:s');
  189. /** 失败回调:写入 Redis 任务状态为「失败」,并返回统一错误结构 */
  190. $fail = function ($msg) use ($data) {
  191. if (!empty($data['task_id'])) {
  192. try {
  193. $redis = getTaskRedis();
  194. $redis->set("img_to_img_task:" . $data['task_id'], json_encode([
  195. 'status' => ImageService::TASK_STATUS_FAILED,
  196. 'msg' => $msg,
  197. 'error' => $msg,
  198. 'completed_at' => date('Y-m-d H:i:s')
  199. ], JSON_UNESCAPED_UNICODE), ['EX' => 300]);
  200. } catch (\Exception $e) {
  201. // Redis 不可用时仍返回错误,不阻断主流程
  202. }
  203. }
  204. return ['code' => 1, 'msg' => $msg];
  205. };
  206. // ========== 第二步:按 status_type 解析图片、拼提示词 ==========
  207. $productBase64 = null;
  208. $productMime = 'image/png';
  209. $templateBase64 = null;
  210. $templateMime = 'image/png';
  211. $productExt = 'png';
  212. $templateExt = 'png';
  213. $sourceProductPath = '';
  214. $sourceTemplatePath = '';
  215. $texttoimageSaveDir = '';
  216. $texttoimageDateSegment = '';
  217. $whiteBgSaveDir = '';
  218. $whiteBgDateSegment = '';
  219. $productDbPath = '';
  220. $promptContent = '';
  221. if ($statusType === 'texttoimage') {
  222. $inputOldImg = $oldImg !== '' ? $oldImg : $productImg;
  223. $inputRefImg = $refImg !== '' ? $refImg : $templateImg;
  224. if (preg_match('/data:image\/(png|jpg|jpeg|webp);base64,(.+)$/is', $inputOldImg, $pm)) {
  225. $productBase64 = preg_replace('/\s+/', '', $pm[2]);
  226. $ext = strtolower($pm[1]);
  227. $productMime = ($ext === 'jpg' ? 'image/jpeg' : 'image/' . $ext);
  228. $productExt = ($ext === 'jpeg' ? 'jpg' : $ext);
  229. } elseif ($inputOldImg !== '') {
  230. $sourceProductPath = ltrim(str_replace('\\', '/', $inputOldImg), '/');
  231. try {
  232. $productImgRaw = AIGatewayService::file_get_contents(Common::ossFullUrl((string)$inputOldImg));
  233. $productBase64 = $productImgRaw['base64Data'];
  234. $productMime = $productImgRaw['mimeType'];
  235. } catch (\Exception $e) {
  236. return $fail('读取原图失败: ' . $e->getMessage());
  237. }
  238. } else {
  239. return $fail('原图不能为空');
  240. }
  241. if ($inputRefImg !== '') {
  242. if (preg_match('/data:image\/(png|jpg|jpeg|webp);base64,(.+)$/is', $inputRefImg, $tm)) {
  243. $templateBase64 = preg_replace('/\s+/', '', $tm[2]);
  244. $ext = strtolower($tm[1]);
  245. $templateMime = ($ext === 'jpg' ? 'image/jpeg' : 'image/' . $ext);
  246. $templateExt = ($ext === 'jpeg' ? 'jpg' : $ext);
  247. } else {
  248. $sourceTemplatePath = ltrim(str_replace('\\', '/', $inputRefImg), '/');
  249. try {
  250. $templateImgRaw = AIGatewayService::file_get_contents(Common::ossFullUrl((string)$inputRefImg));
  251. $templateBase64 = $templateImgRaw['base64Data'];
  252. $templateMime = $templateImgRaw['mimeType'];
  253. } catch (\Exception $e) {
  254. return $fail('读取参考图失败: ' . $e->getMessage());
  255. }
  256. }
  257. }
  258. $texttoimageDateSegment = date('Y-m-d');
  259. $rootPath = str_replace('\\', '/', ROOT_PATH);
  260. $texttoimageSaveDir = rtrim($rootPath, '/') . '/public/uploads/texttoimage/' . $texttoimageDateSegment . '/';
  261. if (!is_dir($texttoimageSaveDir)) {
  262. mkdir($texttoimageSaveDir, 0755, true);
  263. }
  264. if ($sourceProductPath === '' && !empty($productBase64)) {
  265. $sourceFile = 'source-' . uniqid() . '.' . $productExt;
  266. $sourceImageData = base64_decode($productBase64);
  267. if ($sourceImageData === false || !file_put_contents($texttoimageSaveDir . $sourceFile, $sourceImageData)) {
  268. return $fail('原图保存失败');
  269. }
  270. $sourceProductPath = 'uploads/texttoimage/' . $texttoimageDateSegment . '/' . $sourceFile;
  271. if (!Common::uploadLocalFileToOss((string)($texttoimageSaveDir . $sourceFile), (string)$sourceProductPath)) {
  272. echo 'OSS 上传原图失败: ' . $sourceProductPath . "\n";
  273. }
  274. }
  275. if ($sourceTemplatePath === '' && !empty($templateBase64)) {
  276. $refFile = 'ref-' . uniqid() . '.' . $templateExt;
  277. $refImageData = base64_decode($templateBase64);
  278. if ($refImageData !== false && file_put_contents($texttoimageSaveDir . $refFile, $refImageData)) {
  279. $sourceTemplatePath = 'uploads/texttoimage/' . $texttoimageDateSegment . '/' . $refFile;
  280. if (!Common::uploadLocalFileToOss((string)($texttoimageSaveDir . $refFile), (string)$sourceTemplatePath)) {
  281. echo 'OSS 上传参考图失败: ' . $sourceTemplatePath . "\n";
  282. }
  283. }
  284. }
  285. $promptContent = $prompt !== '' ? $prompt : '请根据原图生成高质量图片';
  286. } elseif ($statusType === 'ProductImageGeneration') {
  287. preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $productImg, $pm);
  288. if (empty($pm)) {
  289. return $fail('产品图未找到图片数据');
  290. }
  291. $productBase64 = preg_replace('/\s+/', '', $pm[2]);
  292. $productMime = ($pm[1] === 'jpg' ? 'image/jpeg' : 'image/' . $pm[1]);
  293. $productExt = $pm[1];
  294. preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $templateImg, $tm);
  295. if (empty($tm)) {
  296. return $fail('模板图未找到图片数据');
  297. }
  298. $templateBase64 = preg_replace('/\s+/', '', $tm[2]);
  299. $templateMime = ($tm[1] === 'jpg' ? 'image/jpeg' : 'image/' . $tm[1]);
  300. $templateExt = $tm[1];
  301. $defaultPrompt = '请完成产品模板替换:
  302. 1. 从产品图提取产品主体、品牌名称、核心文案;
  303. 2. 从模板图继承版式布局、文字排版、色彩风格、背景元素;
  304. 3. 将模板图中的产品和文字替换为产品图的内容;
  305. 4. 最终生成的图片与模板图视觉风格统一,仅替换产品和文字。';
  306. $promptContent = $prompt.$defaultPrompt;
  307. } elseif ($statusType === 'ProductTemplateReplace') {
  308. $productImgRaw = AIGatewayService::file_get_contents(Common::ossFullUrl((string)$productImg));
  309. $productBase64 = $productImgRaw['base64Data'];
  310. $productMime = $productImgRaw['mimeType'];
  311. $templateImgRaw = AIGatewayService::file_get_contents(Common::ossFullUrl((string)$templateImg));
  312. $templateBase64 = $templateImgRaw['base64Data'];
  313. $templateMime = $templateImgRaw['mimeType'];
  314. $defaultPrompt = '请完成产品模板替换:
  315. 1. 从产品图提取产品主体、品牌名称、核心文案;
  316. 2. 从模板图继承版式布局、文字排版、色彩风格、背景元素;
  317. 3. 将模板图中的产品和文字替换为产品图的内容;
  318. 4. 最终生成的图片与模板图视觉风格统一,仅替换产品和文字。';
  319. $promptContent = $prompt.$defaultPrompt;
  320. } elseif ($statusType === 'ProductWhiteBackground') {
  321. //处理图片抠图
  322. if (preg_match('/data:image\/(png|jpg|jpeg|webp);base64,(.+)$/is', $productImg, $pm)) {
  323. $productBase64 = preg_replace('/\s+/', '', $pm[2]);
  324. $ext = strtolower($pm[1]);
  325. $productMime = ($ext === 'jpg' ? 'image/jpeg' : 'image/' . $ext);
  326. $productExt = ($ext === 'jpeg' ? 'jpg' : $ext);
  327. } elseif ($productImg !== '') {
  328. try {
  329. $productImgRaw = AIGatewayService::file_get_contents(Common::ossFullUrl((string)$productImg));
  330. $productBase64 = $productImgRaw['base64Data'];
  331. $productMime = $productImgRaw['mimeType'];
  332. $productDbPath = ltrim(str_replace('\\', '/', $productImg), '/');
  333. } catch (\Exception $e) {
  334. return $fail('读取图片失败: ' . $e->getMessage());
  335. }
  336. } else {
  337. return $fail('图片不能为空');
  338. }
  339. $whiteBgDateSegment = date('Y-m-d');
  340. $rootPath = str_replace('\\', '/', ROOT_PATH);
  341. $whiteBgSaveDir = rtrim($rootPath, '/') . '/public/uploads/whitebackground/' . $whiteBgDateSegment . '/';
  342. if (!is_dir($whiteBgSaveDir)) {
  343. mkdir($whiteBgSaveDir, 0755, true);
  344. }
  345. if ($productDbPath === '' && !empty($productBase64)) {
  346. $sourceFile = 'source-' . uniqid() . '.' . $productExt;
  347. $sourceImageData = base64_decode($productBase64);
  348. if ($sourceImageData === false || !file_put_contents($whiteBgSaveDir . $sourceFile, $sourceImageData)) {
  349. return $fail('产品图保存失败');
  350. }
  351. $productDbPath = 'uploads/whitebackground/' . $whiteBgDateSegment . '/' . $sourceFile;
  352. if (!Common::uploadLocalFileToOss((string)($whiteBgSaveDir . $sourceFile), (string)$productDbPath)) {
  353. echo 'OSS 上传产品原图失败: ' . $productDbPath . "\n";
  354. }
  355. }
  356. $promptContent = $prompt !== ''
  357. ? $prompt
  358. : '提取图中产品,更换成白底,保留原始色彩,不改变产品镜头朝向和主要结构,像一个全新的没有瑕疵的产品,并精修产品,清除所有指纹,灰尘与瑕疵';
  359. } else {
  360. return $fail('当前页面未进行配置,请联系管理员开通权限');
  361. }
  362. //调模型生成图像(统一入口,只调一次)
  363. $aiGateway = new AIGatewayService();
  364. /** 成功回调:写入 Redis(含实际模型),供前端轮询 */
  365. $complete = function ($imgPath) use ($data, $aiGateway) {
  366. if (!empty($data['task_id'])) {
  367. try {
  368. $meta = $aiGateway->getLastImg2ImgSuccessMeta();
  369. $payload = [
  370. 'status' => ImageService::TASK_STATUS_COMPLETED,
  371. 'image' => $imgPath,
  372. 'image_url' => $imgPath,
  373. 'completed_at' => date('Y-m-d H:i:s'),
  374. ];
  375. if (!empty($meta)) {
  376. $payload['model'] = $meta['model_name'];
  377. $payload['model_info'] = $meta;
  378. }
  379. $redis = getTaskRedis();
  380. $redis->set("img_to_img_task:" . $data['task_id'], json_encode($payload, JSON_UNESCAPED_UNICODE), ['EX' => 300]);
  381. } catch (\Exception $e) {
  382. // Redis 不可用时图片已生成,不影响 return
  383. }
  384. }
  385. };
  386. try {
  387. $res = $aiGateway->buildRequestData(
  388. $statusVal,
  389. $model,
  390. $promptContent,
  391. $size,
  392. (string)$productBase64,
  393. $productMime,
  394. (string)$templateBase64,
  395. $templateMime
  396. );
  397. } catch (\Exception $e) {
  398. return $fail($e->getMessage());
  399. }
  400. $generatedBase64 = $aiGateway->extractImageBase64FromResponse($res);
  401. if (!$generatedBase64) {
  402. $aiGateway->logImageResponseDebug($res, $data['task_id'] ?? '');
  403. return $fail($aiGateway->describeImageExtractFailure($res));
  404. }
  405. $generatedImageData = base64_decode($generatedBase64);
  406. if ($generatedImageData === false || strlen($generatedImageData) < 100) {
  407. return $fail('图片Base64解码失败');
  408. }
  409. //存入数据库操作(按 status_type 分支)
  410. if ($statusType === 'texttoimage') {
  411. $fileName = 'text2img-' . date('YmdHis') . '-' . uniqid() . '.png';
  412. $fullPath = $texttoimageSaveDir . $fileName;
  413. if (!file_put_contents($fullPath, $generatedImageData)) {
  414. return $fail('图片保存失败');
  415. }
  416. $dbImgPath = 'uploads/texttoimage/' . $texttoimageDateSegment . '/' . $fileName;
  417. if (!Common::uploadLocalFileToOss((string)$fullPath, (string)$dbImgPath)) {
  418. echo 'OSS 上传生成图失败: ' . $dbImgPath . "\n";
  419. }
  420. $sizeForDb = 0;
  421. if ($width !== '' && is_numeric($width)) {
  422. $sizeForDb = (float)$width;
  423. } elseif ($size !== '' && is_numeric($size)) {
  424. $sizeForDb = (float)$size;
  425. }
  426. Db::name('ai_text_image')->insert([
  427. 'old_img' => $sourceProductPath,
  428. 'ref_img' => $sourceTemplatePath,
  429. 'new_img' => $dbImgPath,
  430. 'prompt' => $prompt,
  431. 'size' => $sizeForDb,
  432. 'sys_id' => $sysId,
  433. 'sys_rq' => $sysrq,
  434. ]);
  435. $complete($dbImgPath);
  436. return [
  437. 'path' => $dbImgPath,
  438. 'success_report' => $aiGateway->formatImg2ImgSuccessReport($dbImgPath),
  439. ];
  440. } elseif ($statusType === 'ProductImageGeneration') {
  441. $rootPath = str_replace('\\', '/', ROOT_PATH);
  442. $saveDir = rtrim($rootPath, '/') . '/public/uploads/Product/' . date('Y-m-d') . '/';
  443. if (!is_dir($saveDir)) {
  444. mkdir($saveDir, 0755, true);
  445. }
  446. $productFile = 'product-' . uniqid() . '.' . $productExt;
  447. $productImageData = base64_decode($productBase64);
  448. if ($productImageData === false || !file_put_contents($saveDir . $productFile, $productImageData)) {
  449. return $fail('产品图保存失败');
  450. }
  451. $productDbPath = 'uploads/Product/' . date('Y-m-d') . '/' . $productFile;
  452. Common::uploadLocalFileToOss((string)($saveDir . $productFile), (string)$productDbPath);
  453. $templateFile = 'template-' . uniqid() . '.' . $templateExt;
  454. $templateImageData = base64_decode($templateBase64);
  455. if ($templateImageData === false || !file_put_contents($saveDir . $templateFile, $templateImageData)) {
  456. return $fail('模板图保存失败');
  457. }
  458. $templateDbPath = 'uploads/Product/' . date('Y-m-d') . '/' . $templateFile;
  459. Common::uploadLocalFileToOss((string)($saveDir . $templateFile), (string)$templateDbPath);
  460. $fileName = uniqid() . '.png';
  461. if (!file_put_contents($saveDir . $fileName, $generatedImageData)) {
  462. return $fail('生成图保存失败');
  463. }
  464. $generatedDbPath = 'uploads/Product/' . date('Y-m-d') . '/' . $fileName;
  465. Common::uploadLocalFileToOss((string)($saveDir . $fileName), (string)$generatedDbPath);
  466. Db::name('product_image_generate')->insert([
  467. 'prompt' => $prompt,
  468. 'model' => $model,
  469. 'product_img' => $productDbPath,
  470. 'reference_image' => $templateDbPath,
  471. 'generated_image' => $generatedDbPath,
  472. 'status_val' => $statusVal,
  473. 'size' => $size,
  474. 'sys_id' => $sysId,
  475. 'createTime' => $sysrq,
  476. ]);
  477. $complete($generatedDbPath);
  478. return [
  479. 'path' => $generatedDbPath,
  480. 'success_report' => $aiGateway->formatImg2ImgSuccessReport($generatedDbPath),
  481. ];
  482. } elseif ($statusType === 'ProductWhiteBackground') {
  483. //处理图片抠图
  484. $fileName = 'white-' . date('YmdHis') . '-' . uniqid() . '.png';
  485. $fullPath = $whiteBgSaveDir . $fileName;
  486. if (!file_put_contents($fullPath, $generatedImageData)) {
  487. return $fail('白底图保存失败');
  488. }
  489. $generatedDbPath = 'uploads/whitebackground/' . $whiteBgDateSegment . '/' . $fileName;
  490. if (!Common::uploadLocalFileToOss((string)$fullPath, (string)$generatedDbPath)) {
  491. echo 'OSS 上传白底图失败: ' . $generatedDbPath . "\n";
  492. }
  493. Db::name('ai_white_background')->insert([
  494. 'product_img' => $productDbPath,
  495. 'generated_image' => $generatedDbPath,
  496. 'prompt' => $prompt,
  497. 'model' => $model,
  498. 'size' => $size,
  499. 'status_val' => $statusVal,
  500. 'sys_id' => $sysId,
  501. 'createTime' => $sysrq,
  502. ]);
  503. $complete($generatedDbPath);
  504. return [
  505. 'path' => $generatedDbPath,
  506. 'success_report' => $aiGateway->formatImg2ImgSuccessReport($generatedDbPath),
  507. ];
  508. } elseif ($statusType === 'ProductTemplateReplace') {
  509. $product = Db::name('product')->where('id', $data['id'])->find();
  510. if (empty($product)) {
  511. return $fail('产品不存在');
  512. }
  513. $productCode = $product['product_code'];
  514. $productPrefix = substr($productCode, 0, 9);
  515. $rootPath = str_replace('\\', '/', ROOT_PATH);
  516. $saveDir = rtrim($rootPath, '/') . '/public/uploads/merchant/' . $productPrefix . '/' . $productCode . '/newimg/';
  517. if (!is_dir($saveDir)) {
  518. mkdir($saveDir, 0755, true);
  519. }
  520. $fileName = 'img2img-' . date('YmdHis') . '-' . uniqid() . '.png';
  521. $fullPath = $saveDir . $fileName;
  522. if (!file_put_contents($fullPath, $generatedImageData)) {
  523. return $fail('图片保存失败');
  524. }
  525. $dbImgPath = 'uploads/merchant/' . $productPrefix . '/' . $productCode . '/newimg/' . $fileName;
  526. Common::uploadLocalFileToOss((string)$fullPath, (string)$dbImgPath);
  527. Db::name('product')->where('id', $data['id'])->update([
  528. 'createTime' => date('Y-m-d H:i:s'),
  529. 'content' => $data['prompt'],
  530. 'product_new_img' => $dbImgPath
  531. ]);
  532. Db::name('product_image')->insert([
  533. 'product_id' => $data['id'],
  534. 'product_new_img' => $dbImgPath,
  535. 'product_content' => $data['prompt'],
  536. 'template_id' => $data['template_id'],
  537. 'createTime' => date('Y-m-d H:i:s'),
  538. ]);
  539. $complete($dbImgPath);
  540. return [
  541. 'path' => $dbImgPath,
  542. 'success_report' => $aiGateway->formatImg2ImgSuccessReport($dbImgPath),
  543. ];
  544. } else {
  545. return $fail('当前页面未进行配置,请联系管理员开通权限');
  546. }
  547. }
  548. public function ImageToImage($fileName, $outputDirRaw, $new_image_url, $width, $height)
  549. {
  550. // $rootPath = str_replace('\\', '/', ROOT_PATH);
  551. // $outputDir = rtrim($rootPath . 'public/' . ltrim($outputDirRaw, '/'), '/') . '/';
  552. // $dateDir = date('Y-m-d') . '/';
  553. // $fullBaseDir = $outputDir . $dateDir;
  554. // // 创建主目录和 imgtoimg 子目录
  555. // if (!is_dir($fullBaseDir)) {
  556. // mkdir($fullBaseDir, 0755, true);
  557. // }
  558. // $imgtoimgDir = $fullBaseDir . '1024x1303/';
  559. // if (!is_dir($imgtoimgDir)) {
  560. // mkdir($imgtoimgDir, 0755, true);
  561. // }
  562. // // 查询数据库原图记录
  563. // $record = Db::name('text_to_image')
  564. // ->where('old_image_url', 'like', "%{$fileName}")
  565. // ->order('id desc')
  566. // ->find();
  567. // if (!$record) {
  568. // return json(['code' => 1, 'msg' => '没有找到匹配的图像记录']);
  569. // }
  570. // // 调用 AI 图生图 API
  571. // $ai = new AIGatewayService();
  572. // $res = $ai->txt2imgWithControlNet('', $new_image_url);
  573. // if (!isset($res['code']) || $res['code'] !== 0) {
  574. // return json(['code' => 1, 'msg' => $res['msg'] ?? '图像生成失败']);
  575. // }
  576. // // 生成保存文件路径
  577. // $originalBaseName = pathinfo($new_image_url, PATHINFO_FILENAME);
  578. // $finalFileName = $originalBaseName . '.png';
  579. // $savePath = $imgtoimgDir . $finalFileName;
  580. // // 写入图像文件
  581. // if (!file_put_contents($savePath, base64_decode($res['data']['base64']))) {
  582. // return json(['code' => 1, 'msg' => '图像保存失败,请检查目录权限']);
  583. // }
  584. // // 图生图结果同步 OSS(失败不阻断)
  585. // $relativeImgPath = rtrim($outputDirRaw, '/') . '/' . $dateDir . '1024x1303/' . $finalFileName;
  586. // Common::uploadLocalFileToOss((string)$savePath, (string)$relativeImgPath);
  587. // // 构造相对路径用于数据库
  588. // // 更新数据库记录
  589. // Db::name('text_to_image')->where('id', $record['id'])->update([
  590. // 'imgtoimg_url' => $relativeImgPath,
  591. // 'status_name' => '图生图',
  592. // 'error_msg' => '',
  593. // 'update_time' => date('Y-m-d H:i:s')
  594. // ]);
  595. // // 返回成功响应
  596. // return "成功";
  597. }
  598. }