Index.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. <?php
  2. namespace app\api\controller;
  3. use app\common\controller\Api;
  4. use app\service\AIGatewayService;
  5. use think\Db;
  6. use think\Exception;
  7. /**
  8. * 首页接口
  9. */
  10. class Index extends Api
  11. {
  12. protected $noNeedLogin = ['*'];
  13. protected $noNeedRight = ['*'];
  14. /**
  15. * 首页
  16. */
  17. public function index()
  18. {
  19. $this->success('请求成功');
  20. }
  21. /**
  22. * 图生文接口
  23. */
  24. public function img_to_txt() {
  25. $params = $this->request->param();
  26. $prompt = trim($params['prompt']);
  27. $old_path = trim($params['path']);
  28. $model = trim($params['model']);
  29. $status_val = trim($params['status_val']);
  30. // 参数验证
  31. if (empty($prompt) || empty($old_path) || empty($model)) {
  32. return json([
  33. 'code' => 1,
  34. 'msg' => '缺少必要参数',
  35. 'data' => null
  36. ]);
  37. }
  38. $aiGateway = new AIGatewayService();
  39. // 获取图片的base64数据和MIME类型
  40. $imageData = AIGatewayService::file_get_contents($old_path);
  41. $base64Data = $imageData['base64Data'];
  42. $mimeType = $imageData['mimeType'];
  43. $formattedPrompt = "1. 输出语言:所有内容必须为纯简体中文,禁止出现任何英文、拼音、数字、注释、解释性文字、引导语、示例、标点外的特殊符号;
  44. 2. 第一步(提取原图产品):
  45. - 用1句完整中文描述原图产品,字数控制在50字以内;
  46. - 必须包含「主体细节、产品名称、商标、类型、颜色、风格」核心;
  47. 3. 第二步(生成新图提示词):
  48. - 仅替换【模板提示词】中「产品主体」相关内容为第一步的产品描述;
  49. - 严格保留模板中「设计风格、光影、背景、比例、排版」等非产品相关信息;
  50. - 完全排除模板中的标题类信息,仅保留提示词核心内容;
  51. - 替换后必须完全保留原图产品的核心特征,禁止修改模板非产品核心信息;
  52. 4. 输出格式:不允许添加任何解释、引导、说明、示例、备注,仅返回「产品描述 + 替换后提示词」,直接输出纯文本;
  53. 【模板提示词】{$prompt}";
  54. $result = $aiGateway->callGptApi($model, $formattedPrompt, $mimeType, $base64Data);
  55. // Gemini模型响应格式处理
  56. $imgToTxtContent = $result['candidates'][0]['content']['parts'][0]['text'];
  57. // // 图生文成功后,调用文生文功能(gemini-2.0-flash)
  58. $txtToTxtPrompt = "转换成中文格式,去掉其他特殊符号,不允许添加任何解释、引导、说明、示例等文字:\n\n{$imgToTxtContent}";
  59. $txtToTxtResult = $aiGateway->txtGptApi($txtToTxtPrompt, 'gemini-2.0-flash');
  60. $finalContent = $txtToTxtResult['candidates'][0]['content']['parts'][0]['text'];
  61. // 处理调试输出内容
  62. $debugContent = json_encode($finalContent, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
  63. // 去掉特殊符号(只保留字母、数字、中文、常用标点和换行)
  64. $debugContent = preg_replace('/[^\p{Han}\w\s\n\r\.,,。!!??\-\::;;]/u', '', $debugContent);
  65. // 去掉第一个冒号前的文字
  66. $debugContent = preg_replace('/^[^:]+:/', '', $debugContent);
  67. return json([
  68. 'code' => 0,
  69. 'msg' => '处理成功',
  70. 'data' => [
  71. 'content' => $debugContent,
  72. 'english_content' => $imgToTxtContent
  73. ]
  74. ]);
  75. }
  76. /**
  77. * 文生图接口
  78. */
  79. public function txt_to_img() {
  80. $params = $this->request->param();
  81. $prompt = trim($params['prompt']);
  82. $model = trim($params['model']);
  83. $status_val = trim($params['status_val']);
  84. $size = trim($params['size']);
  85. // 获取产品信息
  86. $product = Db::name('product')->where('id', 1)->find();
  87. if (empty($product)) {
  88. return json([
  89. 'code' => 1,
  90. 'msg' => '产品不存在',
  91. 'data' => null
  92. ]);
  93. }
  94. $product_code = $product['product_code'];
  95. $product_code_prefix = substr($product_code, 0, 9); // 前九位
  96. // 构建URL路径(使用正斜杠)
  97. $url_path = '/uploads/merchant/' . $product_code_prefix . '/' . $product_code . '/newimg/';
  98. // 构建物理路径(使用正斜杠确保统一格式)
  99. $save_path = ROOT_PATH . 'public' . '/' . 'uploads' . '/' . 'merchant' . '/' . $product_code_prefix . '/' . $product_code . '/' . 'newimg' . '/';
  100. // 移除ROOT_PATH中可能存在的反斜杠,确保统一使用正斜杠
  101. $save_path = str_replace('\\', '/', $save_path);
  102. // 自动创建文件夹(如果不存在)
  103. if (!is_dir($save_path)) {
  104. mkdir($save_path, 0755, true);
  105. }
  106. // 调用AI生成图片
  107. $aiGateway = new AIGatewayService();
  108. $res = $aiGateway->callDalleApi($prompt, $model, $size);
  109. // 提取base64图片数据
  110. if (isset($res['candidates'][0]['content']['parts'][0]['text'])) {
  111. $text_content = $res['candidates'][0]['content']['parts'][0]['text'];
  112. // 匹配base64图片数据
  113. preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $text_content, $matches);
  114. if (empty($matches)) {
  115. return json([
  116. 'code' => 1,
  117. 'msg' => '未找到图片数据',
  118. 'data' => null
  119. ]);
  120. }
  121. $image_type = $matches[1];
  122. $base64_data = $matches[2];
  123. // 解码base64数据
  124. $image_data = base64_decode($base64_data);
  125. if ($image_data === false) {
  126. return json([
  127. 'code' => 1,
  128. 'msg' => '图片解码失败',
  129. 'data' => null
  130. ]);
  131. }
  132. // 生成唯一文件名(包含扩展名)
  133. $file_name = uniqid() . '.' . $image_type;
  134. $full_file_path = $save_path . $file_name;
  135. // 保存图片到文件系统
  136. if (!file_put_contents($full_file_path, $image_data)) {
  137. return json([
  138. 'code' => 1,
  139. 'msg' => '图片保存失败',
  140. 'data' => null
  141. ]);
  142. }
  143. // 生成数据库存储路径(使用正斜杠格式)
  144. $db_img_path = $url_path . $file_name;
  145. Db::name('product')->where('id', 1)->update(['product_new_img' => $db_img_path]);
  146. return json([
  147. 'code' => 0,
  148. 'msg' => '文生图请求成功并保存图片',
  149. 'data' => [
  150. 'img' => $db_img_path,
  151. 'product_id' => 1
  152. ]
  153. ]);
  154. } else {
  155. return json([
  156. 'code' => 1,
  157. 'msg' => 'AI返回格式错误',
  158. 'data' => null
  159. ]);
  160. }
  161. }
  162. /**
  163. * 图生图本地测试
  164. * 原图+参考图+提示词=生成新图
  165. * 已替换为公开测试图(免本地文件)+ 通用提示词
  166. */
  167. public function GET_ImgToImg()
  168. {
  169. // 【通用简化提示词】:明确图生图核心需求,适配测试场景
  170. $prompt = '参考第二张模板图片的轻奢设计风格、光影布局、纯色背景和产品展示比例,将第一张可乐产品图的主体替换到模板的核心位置,保留模板的所有非产品设计元素,生成1:1高清产品效果图,无水印、无多余文字,画质清晰';
  171. $size = '1:1';// 生成图片比例
  172. $model = 'gemini-3-pro-image-preview'; // 当前使用的模型
  173. $numImages = 1; // 生成图像数量,gemini模型目前只支持生成1张
  174. // 支持多图像生成的模型:dall-e-3, black-forest-labs/FLUX.1-kontext-pro, gpt-image-1
  175. // 这些模型通过设置 'n' 参数来指定生成图像数量
  176. // 检查模型是否支持多图像生成
  177. $supportMultiImages = in_array($model, ['dall-e-3', 'black-forest-labs/FLUX.1-kontext-pro', 'gpt-image-1']);
  178. if ($supportMultiImages && $numImages > 1) {
  179. // 对于支持多图像生成的模型,可以设置生成数量
  180. $n = $numImages;
  181. }
  182. /**************************
  183. * 替换为:公开测试图(直接转Base64,无需本地文件)
  184. * 图1:产品图(可乐罐,通用测试)
  185. * 图2:模版图(轻奢产品展示背景,通用测试)
  186. **************************/
  187. // 产品图:公开可乐图URL
  188. $productImgUrl = 'https://s41.ax1x.com/2026/02/02/pZ4crcj.jpg';
  189. // 模版图:公开轻奢产品展示背景图URL
  190. $templateImgUrl = 'https://s41.ax1x.com/2026/02/02/pZ4cw4S.jpg';
  191. // 封装:URL转纯Base64(去前缀)+ MIME类型,无需本地文件,直接测试
  192. function urlToPureBase64($imgUrl) {
  193. $imgContent = file_get_contents($imgUrl);
  194. if (!$imgContent) throw new Exception("图片URL读取失败");
  195. // 通过文件扩展名确定MIME类型
  196. $extension = strtolower(pathinfo($imgUrl, PATHINFO_EXTENSION));
  197. $mimeTypes = [
  198. 'jpg' => 'image/jpeg',
  199. 'jpeg' => 'image/jpeg',
  200. 'png' => 'image/png',
  201. 'gif' => 'image/gif',
  202. 'webp' => 'image/webp'
  203. ];
  204. $mime = $mimeTypes[$extension] ?? 'image/jpeg';
  205. $base64 = base64_encode($imgContent);
  206. return ['mime' => $mime, 'base64' => $base64];
  207. }
  208. try {
  209. // 获取两张测试图的纯Base64和MIME
  210. $productImg = urlToPureBase64($productImgUrl);
  211. $templateImg = urlToPureBase64($templateImgUrl);
  212. // ########## 构造API请求参数(适配gemini-3-pro-image-preview) ##########
  213. $data = [
  214. "contents" => [
  215. [
  216. "role" => "user",
  217. "parts" => [
  218. ["text" => $prompt],
  219. // 产品图
  220. ["inlineData" => [
  221. "mimeType" => $productImg['mime'],
  222. "data" => $productImg['base64']
  223. ]],
  224. // 模版图
  225. ["inlineData" => [
  226. "mimeType" => $templateImg['mime'],
  227. "data" => $templateImg['base64']
  228. ]]
  229. ]
  230. ]
  231. ],
  232. "generationConfig" => [
  233. "responseModalities" => ["IMAGE"],
  234. "imageConfig" => ["aspectRatio" => $size, "quality" => "HIGH"],
  235. "temperature" => 0.6,
  236. "topP" => 0.9,
  237. "maxOutputTokens" => 2048
  238. ]
  239. ];
  240. // ########## 调用API ##########
  241. $apiUrl = 'https://chatapi.onechats.ai/v1beta/models/gemini-3-pro-image-preview:generateContent';
  242. $apiKey = 'sk-9aIV9nx7pJxJFMrB8REtNbhjYuNBxCcnEOwiJDHd6UwmN2eJ';
  243. $result = AIGatewayService::callApi($apiUrl, $apiKey, $data);
  244. // 处理响应
  245. if (isset($result['candidates'][0]['content']['parts'][0]['inlineData']['data'])) {
  246. // 直接从inlineData获取图片数据
  247. $base64_data = $result['candidates'][0]['content']['parts'][0]['inlineData']['data'];
  248. $image_type = 'jpg'; // 默认类型
  249. // 解码base64数据
  250. $image_data = base64_decode($base64_data);
  251. if ($image_data === false) {
  252. return json([
  253. 'code' => 1,
  254. 'msg' => '图片解码失败',
  255. 'data' => null
  256. ]);
  257. }
  258. // 保存图片
  259. $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  260. if (!is_dir($saveDir)) {
  261. mkdir($saveDir, 0755, true);
  262. }
  263. $fileName = 'img2img-' . time() . '.' . $image_type;
  264. $savePath = $saveDir . $fileName;
  265. if (!file_put_contents($savePath, $image_data)) {
  266. return json([
  267. 'code' => 1,
  268. 'msg' => '图片保存失败',
  269. 'data' => null
  270. ]);
  271. }
  272. // 生成访问路径
  273. $accessPath = '/uploads/img2img/' . $fileName;
  274. return json([
  275. 'code' => 0,
  276. 'msg' => '图生图已生成',
  277. 'data' => [
  278. 'image' => $accessPath,
  279. 'model' => $model,
  280. 'support_multi_images' => $supportMultiImages
  281. ]
  282. ]);
  283. } else if (isset($result['candidates'][0]['content']['parts'][0]['text'])) {
  284. // 尝试从文本响应中提取图片
  285. $text_content = $result['candidates'][0]['content']['parts'][0]['text'];
  286. preg_match('/data:image\/(png|jpg|jpeg);base64,([^"]+)/', $text_content, $matches);
  287. if (empty($matches)) {
  288. return json([
  289. 'code' => 1,
  290. 'msg' => '未找到图片数据',
  291. 'data' => null
  292. ]);
  293. }
  294. $image_type = $matches[1];
  295. $base64_data = $matches[2];
  296. // 解码base64数据
  297. $image_data = base64_decode($base64_data);
  298. if ($image_data === false) {
  299. return json([
  300. 'code' => 1,
  301. 'msg' => '图片解码失败',
  302. 'data' => null
  303. ]);
  304. }
  305. // 保存图片
  306. $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  307. if (!is_dir($saveDir)) {
  308. mkdir($saveDir, 0755, true);
  309. }
  310. $fileName = 'img2img-' . time() . '.' . $image_type;
  311. $savePath = $saveDir . $fileName;
  312. if (!file_put_contents($savePath, $image_data)) {
  313. return json([
  314. 'code' => 1,
  315. 'msg' => '图片保存失败',
  316. 'data' => null
  317. ]);
  318. }
  319. // 生成访问路径
  320. $accessPath = '/uploads/img2img/' . $fileName;
  321. return json([
  322. 'code' => 0,
  323. 'msg' => '图生图已生成',
  324. 'data' => [
  325. 'image' => $accessPath,
  326. 'model' => $model,
  327. 'support_multi_images' => $supportMultiImages
  328. ]
  329. ]);
  330. } else {
  331. return json([
  332. 'code' => 1,
  333. 'msg' => 'API返回格式错误',
  334. 'data' => null
  335. ]);
  336. }
  337. } catch (Exception $e) {
  338. return json([
  339. 'code' => 1,
  340. 'msg' => 'API调用失败:' . $e->getMessage(),
  341. 'data' => null
  342. ]);
  343. }
  344. }
  345. /**
  346. * 图生图本地测试
  347. */
  348. public function imgtowimg()
  349. {
  350. $prompt = $this->request->param('prompt', '');
  351. $imgRelPath = 'uploads/operate/ai/Preview/arr/0835006071623.png';
  352. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  353. if (!file_exists($imgPath)) {
  354. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  355. }
  356. $imgData = file_get_contents($imgPath);
  357. $base64Img = 'data:image/png;base64,' . base64_encode($imgData);
  358. $params = [
  359. 'prompt' => $prompt,
  360. 'negative_prompt' => '(deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy',
  361. 'steps' => 20,
  362. 'sampler_name' => 'DPM++ 2M SDE',
  363. 'cfg_scale' => 7,
  364. 'seed' => -1,
  365. 'width' => 1024,
  366. 'height' => 1303,
  367. 'override_settings' => [
  368. 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting',
  369. 'sd_vae' => 'vae-ft-mse-840000-ema-pruned',
  370. 'CLIP_stop_at_last_layers' => 2
  371. ],
  372. 'clip_skip' => 2,
  373. 'alwayson_scripts' => [
  374. 'controlnet' => [
  375. 'args' => [[
  376. 'enabled' => true,
  377. 'input_image' => $base64Img,
  378. 'module' => 'inpaint_only+lama',
  379. 'model' => 'control_v11p_sd15_inpaint_fp16 [be8bc0ed]',
  380. 'weight' => 1,
  381. 'resize_mode' => 'Resize and Fill',
  382. 'pixel_perfect' => false,
  383. 'control_mode' => 'ControlNet is more important',
  384. 'starting_control_step' => 0,
  385. 'ending_control_step' => 1
  386. ]]
  387. ]
  388. ]
  389. ];
  390. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/txt2img";
  391. $headers = ['Content-Type: application/json'];
  392. $ch = curl_init();
  393. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  394. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  395. curl_setopt($ch, CURLOPT_POST, true);
  396. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  397. curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params, JSON_UNESCAPED_UNICODE));
  398. curl_setopt($ch, CURLOPT_TIMEOUT, 180);
  399. $response = curl_exec($ch);
  400. $error = curl_error($ch);
  401. curl_close($ch);
  402. if ($error) {
  403. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  404. }
  405. $data = json_decode($response, true);
  406. if (!isset($data['images'][0])) {
  407. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  408. }
  409. $resultImg = base64_decode($data['images'][0]);
  410. $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  411. if (!is_dir($saveDir)) {
  412. mkdir($saveDir, 0755, true);
  413. }
  414. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  415. $fileName = $originalBaseName . '-' . time() . '-1024x1248.png';
  416. $savePath = $saveDir . $fileName;
  417. file_put_contents($savePath, $resultImg);
  418. return json([
  419. 'code' => 0,
  420. 'msg' => '图像生成成功',
  421. 'data' => [
  422. 'origin_url' => '/uploads/img2img/' . $fileName
  423. ]
  424. ]);
  425. }
  426. /**
  427. * 后期图像处理-单张图片高清放大处理
  428. */
  429. public function extra_image()
  430. {
  431. // 配置参数
  432. $config = [
  433. 'input_dir' => 'uploads/img2img/',
  434. 'output_dir' => 'uploads/extra_image/',
  435. 'api_url' => 'http://20.0.17.188:45001/sdapi/v1/extra-single-image',
  436. 'timeout' => 120, // 增加超时时间,高清处理可能耗时较长
  437. 'upscale_params' => [
  438. 'resize_mode' => 0,
  439. 'show_extras_results' => true,
  440. 'gfpgan_visibility' => 0, // 人脸修复关闭
  441. 'codeformer_visibility' => 0, // 人脸修复关闭
  442. 'codeformer_weight' => 0,
  443. 'upscaling_resize' => 2.45, // 放大倍数
  444. 'upscaling_crop' => true,
  445. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B', // 主放大模型
  446. 'upscaler_2' => 'None', // 不使用第二放大器
  447. 'extras_upscaler_2_visibility' => 0,
  448. 'upscale_first' => false,
  449. ]
  450. ];
  451. // 输入文件处理
  452. $imgRelPath = '0835006071623-1757406184-1024x1248.png';
  453. $imgPath = ROOT_PATH . 'public/' . $config['input_dir'] . $imgRelPath;
  454. if (!file_exists($imgPath)) {
  455. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  456. }
  457. // 读取并编码图片
  458. try {
  459. $imgData = file_get_contents($imgPath);
  460. if ($imgData === false) {
  461. throw new Exception('无法读取图片文件');
  462. }
  463. $base64Img = base64_encode($imgData);
  464. } catch (Exception $e) {
  465. return json(['code' => 1, 'msg' => '图片处理失败:' . $e->getMessage()]);
  466. }
  467. // 准备API请求数据
  468. $postData = array_merge($config['upscale_params'], ['image' => $base64Img]);
  469. $jsonData = json_encode($postData);
  470. if ($jsonData === false) {
  471. return json(['code' => 1, 'msg' => 'JSON编码失败']);
  472. }
  473. // 调用API进行高清放大
  474. $ch = curl_init();
  475. curl_setopt_array($ch, [
  476. CURLOPT_URL => $config['api_url'],
  477. CURLOPT_RETURNTRANSFER => true,
  478. CURLOPT_POST => true,
  479. CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
  480. CURLOPT_POSTFIELDS => $jsonData,
  481. CURLOPT_TIMEOUT => $config['timeout'],
  482. CURLOPT_CONNECTTIMEOUT => 30,
  483. ]);
  484. $response = curl_exec($ch);
  485. $error = curl_error($ch);
  486. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  487. curl_close($ch);
  488. if ($error) {
  489. return json(['code' => 1, 'msg' => 'API请求失败:' . $error]);
  490. }
  491. if ($httpCode !== 200) {
  492. return json(['code' => 1, 'msg' => 'API返回错误状态码:' . $httpCode]);
  493. }
  494. $data = json_decode($response, true);
  495. if (json_last_error() !== JSON_ERROR_NONE) {
  496. return json(['code' => 1, 'msg' => 'API返回数据解析失败']);
  497. }
  498. if (!isset($data['image']) || empty($data['image'])) {
  499. return json(['code' => 1, 'msg' => '接口未返回有效的图像数据']);
  500. }
  501. // 保存处理后的图片
  502. try {
  503. $resultImg = base64_decode($data['image']);
  504. if ($resultImg === false) {
  505. throw new Exception('Base64解码失败');
  506. }
  507. $saveDir = ROOT_PATH . 'public/' . $config['output_dir'];
  508. if (!is_dir($saveDir) && !mkdir($saveDir, 0755, true)) {
  509. throw new Exception('无法创建输出目录');
  510. }
  511. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  512. $fileName = $originalBaseName . '-hd.png'; // 使用-hd后缀更明确
  513. $savePath = $saveDir . $fileName;
  514. if (file_put_contents($savePath, $resultImg) === false) {
  515. throw new Exception('无法保存处理后的图片');
  516. }
  517. // 返回成功响应
  518. return json([
  519. 'code' => 0,
  520. 'msg' => '图像高清放大处理成功',
  521. 'data' => [
  522. 'url' => '/' . $config['output_dir'] . $fileName,
  523. 'original_size' => filesize($imgPath),
  524. 'processed_size' => filesize($savePath),
  525. 'resolution' => getimagesize($savePath), // 返回新图片的分辨率
  526. ]
  527. ]);
  528. } catch (Exception $e) {
  529. return json(['code' => 1, 'msg' => '保存结果失败:' . $e->getMessage()]);
  530. }
  531. }
  532. /**
  533. * 文本生成图片并保存第一张结果
  534. * @param array $params 请求参数
  535. * @return array 返回结果
  536. */
  537. public function txttowimg()
  538. {
  539. // API配置
  540. $config = [
  541. 'api_url' => 'https://chatapi.onechats.ai/mj/submit/imagine',
  542. 'fetch_url' => 'https://chatapi.onechats.ai/mj/task/',
  543. 'api_key' => 'sk-iURfrAgzAjhZ4PpPLwzmWIAhM7zKfrkwDvyxk4RVBQ4ouJNK',
  544. 'default_prompt' => '一个猫',
  545. 'wait_time' => 3 // 等待生成完成的秒数
  546. ];
  547. try {
  548. // 1. 准备请求数据
  549. $prompt = $config['default_prompt'];
  550. $postData = [
  551. 'botType' => 'MID_JOURNEY',
  552. 'prompt' => $prompt,
  553. 'base64Array' => [],
  554. 'accountFilter' => [
  555. 'channelId' => "",
  556. 'instanceId' => "",
  557. 'modes' => [],
  558. 'remark' => "",
  559. 'remix' => true,
  560. 'remixAutoConsidered' => true
  561. ],
  562. 'notifyHook' => "",
  563. 'state' => ""
  564. ];
  565. // 2. 提交生成请求
  566. $generateResponse = $this->sendApiRequest($config['api_url'], $postData, $config['api_key']);
  567. $generateData = json_decode($generateResponse, true);
  568. if (empty($generateData['result'])) {
  569. throw new Exception('生成失败: '.($generateData['message'] ?? '未知错误'));
  570. }
  571. $taskId = $generateData['result'];
  572. // 3. 等待图片生成完成
  573. sleep($config['wait_time']);
  574. // 4. 获取生成结果
  575. $fetchUrl = $config['fetch_url'].$taskId.'/fetch';
  576. $fetchResponse = $this->sendApiRequest($fetchUrl, [], $config['api_key'], 'GET');
  577. $fetchData = json_decode($fetchResponse, true);
  578. if (empty($fetchData['imageUrl'])) {
  579. throw new Exception('获取图片失败: '.($fetchData['message'] ?? '未知错误'));
  580. }
  581. // 5. 处理返回的图片数组(取第一张)
  582. $imageUrls = is_array($fetchData['imageUrl']) ? $fetchData['imageUrl'] : [$fetchData['imageUrl']];
  583. $firstImageUrl = $imageUrls[0];
  584. // 6. 保存图片到本地
  585. $savePath = $this->saveImage($firstImageUrl);
  586. // 7. 返回结果
  587. return [
  588. 'code' => 200,
  589. 'msg' => '图片生成并保存成功',
  590. 'data' => [
  591. 'local_path' => $savePath,
  592. 'web_url' => request()->domain().$savePath,
  593. 'task_id' => $taskId
  594. ]
  595. ];
  596. } catch (Exception $e) {
  597. // 错误处理
  598. return [
  599. 'code' => 500,
  600. 'msg' => '处理失败: '.$e->getMessage(),
  601. 'data' => null
  602. ];
  603. }
  604. }
  605. /**
  606. * 发送API请求
  607. * @param string $url 请求地址
  608. * @param array $data 请求数据
  609. * @param string $apiKey API密钥
  610. * @param string $method 请求方法
  611. * @return string 响应内容
  612. * @throws Exception
  613. */
  614. private function sendApiRequest($url, $data, $apiKey, $method = 'POST')
  615. {
  616. $ch = curl_init();
  617. curl_setopt_array($ch, [
  618. CURLOPT_URL => $url,
  619. CURLOPT_RETURNTRANSFER => true,
  620. CURLOPT_CUSTOMREQUEST => $method,
  621. CURLOPT_HTTPHEADER => [
  622. 'Authorization: Bearer '.$apiKey,
  623. 'Accept: application/json',
  624. 'Content-Type: application/json'
  625. ],
  626. CURLOPT_POSTFIELDS => $method === 'POST' ? json_encode($data) : null,
  627. CURLOPT_SSL_VERIFYPEER => false,
  628. CURLOPT_SSL_VERIFYHOST => false,
  629. CURLOPT_TIMEOUT => 60,
  630. CURLOPT_FAILONERROR => true
  631. ]);
  632. $response = curl_exec($ch);
  633. $error = curl_error($ch);
  634. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  635. curl_close($ch);
  636. if ($error) {
  637. throw new Exception('API请求失败: '.$error);
  638. }
  639. if ($httpCode < 200 || $httpCode >= 300) {
  640. throw new Exception('API返回错误状态码: '.$httpCode);
  641. }
  642. return $response;
  643. }
  644. /**
  645. * 保存图片到本地
  646. * @param string $imageUrl 图片URL
  647. * @return string 本地保存路径
  648. * @throws Exception
  649. */
  650. private function saveImage($imageUrl)
  651. {
  652. // 1. 创建存储目录
  653. $saveDir = ROOT_PATH.'public'.DS.'uploads'.DS.'midjourney'.DS.date('Ymd');
  654. if (!is_dir($saveDir)) {
  655. mkdir($saveDir, 0755, true);
  656. }
  657. // 2. 生成唯一文件名
  658. $filename = uniqid().'.png';
  659. $localPath = DS.'uploads'.DS.'midjourney'.DS.date('Ymd').DS.$filename;
  660. $fullPath = $saveDir.DS.$filename;
  661. // 3. 下载图片
  662. $ch = curl_init($imageUrl);
  663. curl_setopt_array($ch, [
  664. CURLOPT_RETURNTRANSFER => true,
  665. CURLOPT_FOLLOWLOCATION => true,
  666. CURLOPT_SSL_VERIFYPEER => false,
  667. CURLOPT_CONNECTTIMEOUT => 15
  668. ]);
  669. $imageData = curl_exec($ch);
  670. $error = curl_error($ch);
  671. curl_close($ch);
  672. if (!$imageData) {
  673. throw new Exception('图片下载失败: '.$error);
  674. }
  675. // 4. 验证图片类型
  676. $imageInfo = getimagesizefromstring($imageData);
  677. if (!in_array($imageInfo['mime'] ?? '', ['image/png', 'image/jpeg'])) {
  678. throw new Exception('下载内容不是有效图片');
  679. }
  680. // 5. 保存文件
  681. if (!file_put_contents($fullPath, $imageData)) {
  682. throw new Exception('图片保存失败');
  683. }
  684. return $localPath;
  685. }
  686. }