WorkOrder.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. <?php
  2. namespace app\api\controller;
  3. use app\common\controller\Api;
  4. use app\job\ImageJob;
  5. use app\service\ImageService;
  6. use think\App;
  7. use think\Db;
  8. use think\Log;
  9. use think\Queue;
  10. use think\queue\job\Redis;
  11. class WorkOrder extends Api
  12. {
  13. protected $noNeedLogin = ['*'];
  14. protected $noNeedRight = ['*'];
  15. /**
  16. * 出图接口
  17. * 此方法处理图像转换为文本的请求,将图像信息存入队列以供后续处理。
  18. */
  19. public function imageToText()
  20. {
  21. $params = $this->request->param();
  22. $service = new ImageService();
  23. $service->handleImage($params);
  24. $this->success('任务成功提交至队列');
  25. }
  26. /**
  27. * 图生图
  28. * /sdapi/v1/img2img
  29. */
  30. public function imgtowimg()
  31. {
  32. //原图路径
  33. $imgRelPath = 'uploads/operate/ai/Preview/arr/该图案传递了旅行和无忧无虑的心情采用明快的色调充满活力的复古.png';
  34. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  35. if (!file_exists($imgPath)) {
  36. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  37. }
  38. // 原图 base64 编码
  39. $imgData = file_get_contents($imgPath);
  40. $base64Img = base64_encode($imgData);
  41. $initImage = 'data:image/png;base64,' . $base64Img;
  42. // 参数设置
  43. $postData = json_encode([
  44. 'prompt' => '',
  45. 'sampler_name' => 'DPM++ 2M SDE Heun',
  46. 'seed' => -1,
  47. 'steps' => 20,
  48. 'cfg_scale' => 7,
  49. 'denoising_strength' => 0.2,
  50. 'width' => 1024,
  51. 'height' => 2048,
  52. 'inpainting_fill' => 1,
  53. 'resize_mode' => 0, // 自动等比缩放+填充
  54. 'inpaint_full_res' => true,
  55. 'init_images' => [$initImage],
  56. 'override_settings' => [
  57. 'sd_model_checkpoint' => 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]',
  58. 'sd_vae' => 'anything-v4.5.vae.pt' // 设置外挂 VAE 模型
  59. ]
  60. ]);
  61. //调用接口
  62. $apiUrl = "http://20.0.17.233:45001/sdapi/v1/img2img";
  63. $headers = ['Content-Type: application/json'];
  64. $ch = curl_init();
  65. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  66. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  67. curl_setopt($ch, CURLOPT_POST, true);
  68. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  69. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  70. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  71. $response = curl_exec($ch);
  72. $error = curl_error($ch);
  73. curl_close($ch);
  74. if ($error) {
  75. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  76. }
  77. $data = json_decode($response, true);
  78. if (!isset($data['images'][0])) {
  79. return json(['code' => 1, 'msg' => '未返回图像数据']);
  80. }
  81. // 保存结果图像
  82. $resultImg = base64_decode($data['images'][0]);
  83. $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  84. if (!is_dir($saveDir)) {
  85. mkdir($saveDir, 0755, true);
  86. }
  87. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  88. $fileName = $originalBaseName . '.png';
  89. $savePath = $saveDir . $fileName;
  90. file_put_contents($savePath, $resultImg);
  91. return json([
  92. 'code' => 0,
  93. 'msg' => '图像上下扩展生成成功',
  94. 'data' => [
  95. 'origin_url' => '/uploads/img2img/' . $fileName
  96. ]
  97. ]);
  98. }
  99. // public function imgtowimg()
  100. // {
  101. // $prompt = $this->request->param('prompt', '');
  102. // $denoising = (float)$this->request->param('denoising_strength', 0.2);
  103. // $scale = (float)$this->request->param('scale', 2.0);
  104. // $modelName = $this->request->param('model', 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]');
  105. //
  106. // // 原图路径
  107. // $imgRelPath = 'uploads/operate/ai/Preview/arr/0828004096727.png';
  108. // $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  109. //
  110. // if (!file_exists($imgPath)) {
  111. // return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  112. // }
  113. //
  114. // // 获取原图尺寸 × 倍数
  115. // list($originW, $originH) = getimagesize($imgPath);
  116. // $targetW = intval($originW * $scale);
  117. // $targetH = intval($originH * $scale);
  118. //
  119. // // base64 编码
  120. // $imgData = file_get_contents($imgPath);
  121. // $base64Img = base64_encode($imgData);
  122. // $initImage = 'data:image/png;base64,' . $base64Img;
  123. //
  124. // // 请求体
  125. // $postData = json_encode([
  126. // 'prompt' => $prompt,
  127. // 'steps' => 30,
  128. // 'cfg_scale' => 7,
  129. // 'denoising_strength' => $denoising,
  130. // 'width' => $targetW,
  131. // 'height' => $targetH,
  132. // 'resize_mode' => 1,
  133. // 'inpaint_full_res' => true,
  134. // 'inpainting_fill' => 1,
  135. // 'init_images' => [$initImage],
  136. // 'override_settings' => [
  137. // 'sd_model_checkpoint' => $modelName
  138. // ]
  139. // ]);
  140. //
  141. // $apiUrl = "http://20.0.17.233:45001/sdapi/v1/img2img";
  142. // $headers = ['Content-Type: application/json'];
  143. //
  144. // $ch = curl_init();
  145. // curl_setopt($ch, CURLOPT_URL, $apiUrl);
  146. // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  147. // curl_setopt($ch, CURLOPT_POST, true);
  148. // curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  149. // curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  150. // curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  151. // $response = curl_exec($ch);
  152. // $error = curl_error($ch);
  153. // curl_close($ch);
  154. //
  155. // if ($error) {
  156. // return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  157. // }
  158. //
  159. // $data = json_decode($response, true);
  160. // if (!isset($data['images'][0])) {
  161. // return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  162. // }
  163. //
  164. // // 保存图像:原图名 + -1
  165. // $resultImg = base64_decode($data['images'][0]);
  166. // $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  167. // if (!is_dir($saveDir)) {
  168. // mkdir($saveDir, 0755, true);
  169. // }
  170. //
  171. // $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  172. // $fileName = $originalBaseName . '-1.png';
  173. // $savePath = $saveDir . $fileName;
  174. // file_put_contents($savePath, $resultImg);
  175. //
  176. // return json([
  177. // 'code' => 0,
  178. // 'msg' => '图像生成成功',
  179. // 'data' => [
  180. // 'origin_url' => '/uploads/img2img/' . $fileName
  181. // ]
  182. // ]);
  183. // }
  184. /**
  185. * 后期图像处理
  186. * /sdapi/v1/extra-single-image
  187. */
  188. public function extra_image()
  189. {
  190. $imgRelPath = 'uploads/img2img/0828004096727-1.png'; // 图生图结果
  191. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  192. if (!file_exists($imgPath)) {
  193. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  194. }
  195. $imgData = file_get_contents($imgPath);
  196. $base64Img = base64_encode($imgData);
  197. $postData = json_encode([
  198. 'resize_mode' => 0,
  199. 'show_extras_results' => true,
  200. 'gfpgan_visibility' => 0,
  201. 'codeformer_visibility' => 0,
  202. 'codeformer_weight' => 0,
  203. 'upscaling_resize' => 2.45,
  204. 'upscaling_crop' => true,
  205. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
  206. 'upscaler_2' => 'None',
  207. 'extras_upscaler_2_visibility' => 0,
  208. 'upscale_first' => false,
  209. 'image' => $base64Img
  210. ]);
  211. $apiUrl = "http://20.0.17.233:45001/sdapi/v1/extra-single-image";
  212. $headers = ['Content-Type: application/json'];
  213. $ch = curl_init();
  214. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  215. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  216. curl_setopt($ch, CURLOPT_POST, true);
  217. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  218. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  219. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  220. $response = curl_exec($ch);
  221. $error = curl_error($ch);
  222. curl_close($ch);
  223. if ($error) {
  224. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  225. }
  226. $data = json_decode($response, true);
  227. if (!isset($data['image'])) {
  228. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  229. }
  230. // 保存:原图基础名 + -2
  231. $resultImg = base64_decode($data['image']);
  232. $saveDir = ROOT_PATH . 'public/uploads/extra_image/';
  233. if (!is_dir($saveDir)) {
  234. mkdir($saveDir, 0755, true);
  235. }
  236. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  237. $fileName = $originalBaseName . '-2.png';
  238. $savePath = $saveDir . $fileName;
  239. file_put_contents($savePath, $resultImg);
  240. return json([
  241. 'code' => 0,
  242. 'msg' => '图像后处理成功',
  243. 'data' => [
  244. 'url' => '/uploads/extra_image/' . $fileName
  245. ]
  246. ]);
  247. }
  248. /**
  249. * 查询队列列表
  250. * 统计文件对应的队列情况
  251. */
  252. public function get_queue_logs()
  253. {
  254. $params = $this->request->param('old_image_file', '');
  255. $queue_logs = Db::name('queue_logs')
  256. ->where('old_image_file', $params)
  257. ->order('id desc')
  258. ->select();
  259. $result = []; //初始化变量,避免未定义错误
  260. foreach ($queue_logs as &$log) {
  261. $taskId = $log['id'];
  262. $statusCount = Db::name('image_task_log')
  263. ->field('status, COUNT(*) as count')
  264. ->where('task_id', $taskId)
  265. ->where('mod_rq', null)
  266. ->group('status')
  267. ->select();
  268. $log['已完成数量'] = 0;
  269. $log['处理中数量'] = 0;
  270. $log['排队中的数量'] = 0;
  271. $log['失败数量'] = 0;
  272. foreach ($statusCount as $item) {
  273. switch ($item['status']) {
  274. case 0:
  275. $log['排队中的数量'] = $item['count'];
  276. break;
  277. case 1:
  278. $log['处理中数量'] = $item['count'];
  279. break;
  280. case 2:
  281. $log['已完成数量'] = $item['count'];
  282. break;
  283. case -1:
  284. $log['失败数量'] = $item['count'];
  285. break;
  286. }
  287. }
  288. if ($log['处理中数量'] > 0) {
  289. $result[] = $log;
  290. }
  291. }
  292. return json([
  293. 'code' => 0,
  294. 'msg' => '查询成功',
  295. 'data' => $result,
  296. 'count' => count($result)
  297. ]);
  298. }
  299. /**
  300. * 查询总队列状态(统计当前处理的数据量)
  301. */
  302. public function queueStats()
  303. {
  304. $statusList = Db::name('image_task_log')
  305. ->field('status, COUNT(*) as total')
  306. ->where('mod_rq', null)
  307. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  308. ->group('status')
  309. ->select();
  310. $statusCount = [];
  311. foreach ($statusList as $item) {
  312. $statusCount[$item['status']] = $item['total'];
  313. }
  314. // 总数为所有状态和
  315. $total = array_sum($statusCount);
  316. //获取队列当前状态
  317. $statusText = Db::name('queue_logs')->order('id desc')->value('status');
  318. return json([
  319. 'code' => 0,
  320. 'msg' => '获取成功',
  321. 'data' => [
  322. '总任务数' => $total,
  323. '待处理' => $statusCount[0] ?? 0,
  324. '处理中' => $statusCount[1] ?? 0,
  325. '成功' => $statusCount[2] ?? 0,
  326. '失败' => $statusCount[-1] ?? 0,
  327. '当前状态' => $statusText
  328. ]
  329. ]);
  330. }
  331. /**
  332. * 显示当前运行中的队列监听进程
  333. */
  334. public function viewQueueStatus()
  335. {
  336. $redis = new \Redis();
  337. $redis->connect('127.0.0.1', 6379);
  338. $redis->auth('123456');
  339. $redis->select(15);
  340. $key = 'queues:imgtotxt';
  341. // 判断 key 是否存在,避免报错
  342. if (!$redis->exists($key)) {
  343. return json([
  344. 'code' => 0,
  345. 'msg' => '查询成功,队列为空',
  346. 'count' => 0,
  347. 'tasks_preview' => []
  348. ]);
  349. }
  350. $count = $redis->lLen($key);
  351. $list = $redis->lRange($key, 0, 9);
  352. // 解码 JSON 内容,确保每一项都有效
  353. $parsed = array_filter(array_map(function ($item) {
  354. return json_decode($item, true);
  355. }, $list), function ($item) {
  356. return !is_null($item);
  357. });
  358. return json([
  359. 'code' => 0,
  360. 'msg' => '查询成功',
  361. 'count' => $count,
  362. 'tasks_preview' => $parsed
  363. ]);
  364. }
  365. /**
  366. * 清空队列并删除队列日志记录
  367. */
  368. public function stopQueueProcesses()
  369. {
  370. $redis = new \Redis();
  371. $redis->connect('127.0.0.1', 6379);
  372. $redis->auth('123456');
  373. $redis->select(15);
  374. $key_txttoimg = 'queues:txttoimg';
  375. $key_txttotxt = 'queues:txttotxt';
  376. $key_imgtotxt = 'queues:imgtotxt';
  377. $count = $redis->lLen($key_txttoimg) + $redis->lLen($key_txttotxt) + $redis->lLen($key_imgtotxt);
  378. if ($count === 0) {
  379. return json([
  380. 'code' => 1,
  381. 'msg' => '暂无队列需要停止'
  382. ]);
  383. }
  384. // 清空 Redis 队列
  385. $redis->del($key_txttoimg);
  386. $redis->del($key_txttotxt);
  387. $redis->del($key_imgtotxt);
  388. $counts = Db::name('image_task_log')
  389. ->where('log', '队列中')
  390. ->whereOr('status', 1)
  391. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  392. ->update([
  393. 'status' => "-1",
  394. 'log' => '清空取消队列',
  395. 'mod_rq' => date('Y-m-d H:i:s')
  396. ]);
  397. return json([
  398. 'code' => 0,
  399. 'msg' => '成功停止队列任务'
  400. ]);
  401. }
  402. /**
  403. * 开启队列任务
  404. * 暂时用不到、服务器已开启自动开启队列模式
  405. */
  406. // public function kaiStats()
  407. // {
  408. // // 判断是否已有监听进程在运行
  409. // $check = shell_exec("ps aux | grep 'queue:listen' | grep -v grep");
  410. // if ($check) {
  411. // return json([
  412. // 'code' => 1,
  413. // 'msg' => '监听进程已存在,请勿重复启动'
  414. // ]);
  415. // }
  416. // // 启动监听
  417. // $command = 'nohup php think queue:listen --queue --timeout=300 --sleep=3 --memory=256 > /var/log/job_queue.log 2>&1 &';
  418. // exec($command, $output, $status);
  419. // if ($status === 0) {
  420. // return json([
  421. // 'code' => 0,
  422. // 'msg' => '队列监听已启动'
  423. // ]);
  424. // } else {
  425. // return json([
  426. // 'code' => 1,
  427. // 'msg' => '队列启动失败',
  428. // 'output' => $output
  429. // ]);
  430. // }
  431. // }
  432. /**
  433. * 通过店铺ID-查询对应店铺表数据
  434. *
  435. */
  436. public function PatternApi()
  437. {
  438. $params = $this->request->param('pattern_id', '');
  439. $tableName = 'pattern-' . $params;
  440. // 连接 MongoDB
  441. $mongo = Db::connect('mongodb');
  442. // 查询指定 skc 的数据
  443. $data = $mongo->table($tableName)
  444. ->field('
  445. name,
  446. skc,
  447. file
  448. ')
  449. ->where("skc", '0853004152036')
  450. ->select();
  451. $data = json_decode(json_encode($data), true); // 数组
  452. return json([
  453. 'code' => 0,
  454. 'msg' => '获取成功',
  455. 'data' => $data
  456. ]);
  457. }
  458. }