WorkOrder.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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. * 图生图功能 - /sdapi/v1/img2img
  28. */
  29. public function imgtowimg()
  30. {
  31. $prompt = $this->request->param('prompt', ''); // 用户传入的提示词
  32. // $imgRelPath = 'uploads/operate/ai/Preview/arr/0828004096727.png';
  33. // $imgRelPath = 'uploads/operate/ai/Preview/arr/1751019283787.jpg';
  34. $imgRelPath = 'uploads/operate/ai/Preview/arr/圣诞主题复古油画圣诞老人和孩子们乘坐红色雪橇在雪地中滑行充满.png';
  35. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  36. // 校验原图是否存在
  37. if (!file_exists($imgPath)) {
  38. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  39. }
  40. // -------- 参数调整 -------- //
  41. $denoising = 0.2; // 降低重绘幅度
  42. $steps = 10; // 减少迭代步数
  43. $cfgScale = 7;
  44. $clipSkip = 7;
  45. $sampler = 'DPM++ 2M SDE Heun';
  46. $modelName = 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]';
  47. $vaeName = 'anything-v4.5.vae.pt';
  48. $seed = 611075477;
  49. $targetW = 679; // 目标宽度
  50. $targetH = 862; // 目标高度
  51. // -------- 图像编码 -------- //
  52. $imgData = file_get_contents($imgPath);
  53. $base64Img = base64_encode($imgData);
  54. $initImage = 'data:image/png;base64,' . $base64Img;
  55. // -------- 请求体构建 -------- //
  56. $postData = json_encode([
  57. 'prompt' => $prompt,
  58. 'steps' => $steps,
  59. 'cfg_scale' => $cfgScale,
  60. 'denoising_strength' => $denoising,
  61. 'width' => $targetW,
  62. 'height' => $targetH,
  63. 'resize_mode' => 0,
  64. 'sampler_name' => $sampler,
  65. 'seed' => $seed,
  66. 'inpaint_full_res' => true, // 关闭高分辨率重绘
  67. 'inpainting_fill' => 1,
  68. 'init_images' => [$initImage],
  69. 'override_settings' => [
  70. 'sd_model_checkpoint' => $modelName,
  71. 'sd_vae' => $vaeName,
  72. 'CLIP_stop_at_last_layers' => $clipSkip
  73. ],
  74. 'override_settings_restore_afterwards' => true
  75. ]);
  76. // -------- 发送请求到 SD API -------- //
  77. $apiUrl = "http://20.0.17.233:45001/sdapi/v1/img2img";
  78. $headers = ['Content-Type: application/json'];
  79. $ch = curl_init();
  80. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  81. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  82. curl_setopt($ch, CURLOPT_POST, true);
  83. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  84. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  85. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  86. $response = curl_exec($ch);
  87. $error = curl_error($ch);
  88. curl_close($ch);
  89. if ($error) {
  90. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  91. }
  92. $data = json_decode($response, true);
  93. if (!isset($data['images'][0])) {
  94. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  95. }
  96. // -------- 保存生成图像 -------- //
  97. $resultImg = base64_decode($data['images'][0]);
  98. $saveDir = ROOT_PATH . 'public/uploads/img2img/';
  99. if (!is_dir($saveDir)) {
  100. mkdir($saveDir, 0755, true);
  101. }
  102. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  103. $fileName = $originalBaseName . '-' . time() . '-1.png';
  104. $savePath = $saveDir . $fileName;
  105. file_put_contents($savePath, $resultImg);
  106. return json([
  107. 'code' => 0,
  108. 'msg' => '图像生成成功',
  109. 'data' => [
  110. 'origin_url' => '/uploads/img2img/' . $fileName
  111. ]
  112. ]);
  113. }
  114. /**
  115. * 后期图像处理
  116. * /sdapi/v1/extra-single-image
  117. */
  118. public function extra_image()
  119. {
  120. $imgRelPath = 'uploads/img2img/0828004096727-1.png'; // 图生图结果
  121. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  122. if (!file_exists($imgPath)) {
  123. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  124. }
  125. $imgData = file_get_contents($imgPath);
  126. $base64Img = base64_encode($imgData);
  127. $postData = json_encode([
  128. 'resize_mode' => 0,
  129. 'show_extras_results' => true,
  130. 'gfpgan_visibility' => 0,
  131. 'codeformer_visibility' => 0,
  132. 'codeformer_weight' => 0,
  133. 'upscaling_resize' => 2.45,
  134. 'upscaling_crop' => true,
  135. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
  136. 'upscaler_2' => 'None',
  137. 'extras_upscaler_2_visibility' => 0,
  138. 'upscale_first' => false,
  139. 'image' => $base64Img
  140. ]);
  141. $apiUrl = "http://20.0.17.233:45001/sdapi/v1/extra-single-image";
  142. $headers = ['Content-Type: application/json'];
  143. $ch = curl_init();
  144. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  145. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  146. curl_setopt($ch, CURLOPT_POST, true);
  147. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  148. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  149. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  150. $response = curl_exec($ch);
  151. $error = curl_error($ch);
  152. curl_close($ch);
  153. if ($error) {
  154. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  155. }
  156. $data = json_decode($response, true);
  157. if (!isset($data['image'])) {
  158. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  159. }
  160. // 保存:原图基础名 + -2
  161. $resultImg = base64_decode($data['image']);
  162. $saveDir = ROOT_PATH . 'public/uploads/extra_image/';
  163. if (!is_dir($saveDir)) {
  164. mkdir($saveDir, 0755, true);
  165. }
  166. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  167. $fileName = $originalBaseName . '-2.png';
  168. $savePath = $saveDir . $fileName;
  169. file_put_contents($savePath, $resultImg);
  170. return json([
  171. 'code' => 0,
  172. 'msg' => '图像后处理成功',
  173. 'data' => [
  174. 'url' => '/uploads/extra_image/' . $fileName
  175. ]
  176. ]);
  177. }
  178. /**
  179. * 查询队列列表
  180. * 统计文件对应的队列情况
  181. */
  182. public function get_queue_logs()
  183. {
  184. $params = $this->request->param('old_image_file', '');
  185. $queue_logs = Db::name('queue_logs')
  186. ->where('old_image_file', $params)
  187. ->order('id desc')
  188. ->select();
  189. $result = []; //初始化变量,避免未定义错误
  190. foreach ($queue_logs as &$log) {
  191. $taskId = $log['id'];
  192. $statusCount = Db::name('image_task_log')
  193. ->field('status, COUNT(*) as count')
  194. ->where('task_id', $taskId)
  195. ->where('mod_rq', null)
  196. ->group('status')
  197. ->select();
  198. $log['已完成数量'] = 0;
  199. $log['处理中数量'] = 0;
  200. $log['排队中的数量'] = 0;
  201. $log['失败数量'] = 0;
  202. foreach ($statusCount as $item) {
  203. switch ($item['status']) {
  204. case 0:
  205. $log['排队中的数量'] = $item['count'];
  206. break;
  207. case 1:
  208. $log['处理中数量'] = $item['count'];
  209. break;
  210. case 2:
  211. $log['已完成数量'] = $item['count'];
  212. break;
  213. case -1:
  214. $log['失败数量'] = $item['count'];
  215. break;
  216. }
  217. }
  218. // if ($log['排队中的数量'] >$log['已完成数量']) {
  219. // $result[] = $log;
  220. // }
  221. if ($log['排队中的数量']) {
  222. $result[] = $log;
  223. }
  224. // if ($log['处理中数量'] >= 0) {
  225. // $result[] = $log;
  226. // }
  227. }
  228. return json([
  229. 'code' => 0,
  230. 'msg' => '查询成功',
  231. 'data' => $result,
  232. 'count' => count($result)
  233. ]);
  234. }
  235. /**
  236. * 查询总队列状态(统计当前处理的数据量)
  237. */
  238. public function queueStats()
  239. {
  240. $statusList = Db::name('image_task_log')
  241. ->field('status, COUNT(*) as total')
  242. ->where('mod_rq', null)
  243. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  244. ->group('status')
  245. ->select();
  246. $statusCount = [];
  247. foreach ($statusList as $item) {
  248. $statusCount[$item['status']] = $item['total'];
  249. }
  250. // 总数为所有状态和
  251. $total = array_sum($statusCount);
  252. //获取队列当前状态
  253. $statusText = Db::name('queue_logs')->order('id desc')->value('status');
  254. return json([
  255. 'code' => 0,
  256. 'msg' => '获取成功',
  257. 'data' => [
  258. '总任务数' => $total,
  259. '待处理' => $statusCount[0] ?? 0,
  260. '处理中' => $statusCount[1] ?? 0,
  261. '成功' => $statusCount[2] ?? 0,
  262. '失败' => $statusCount[-1] ?? 0,
  263. '当前状态' => $statusText
  264. ]
  265. ]);
  266. }
  267. /**
  268. * 显示当前运行中的队列监听进程
  269. */
  270. public function viewQueueStatus()
  271. {
  272. $redis = new \Redis();
  273. $redis->connect('127.0.0.1', 6379);
  274. $redis->auth('123456');
  275. $redis->select(15);
  276. $key = 'queues:imgtotxt';
  277. // 判断 key 是否存在,避免报错
  278. if (!$redis->exists($key)) {
  279. return json([
  280. 'code' => 0,
  281. 'msg' => '查询成功,队列为空',
  282. 'count' => 0,
  283. 'tasks_preview' => []
  284. ]);
  285. }
  286. $count = $redis->lLen($key);
  287. $list = $redis->lRange($key, 0, 9);
  288. // 解码 JSON 内容,确保每一项都有效
  289. $parsed = array_filter(array_map(function ($item) {
  290. return json_decode($item, true);
  291. }, $list), function ($item) {
  292. return !is_null($item);
  293. });
  294. return json([
  295. 'code' => 0,
  296. 'msg' => '查询成功',
  297. 'count' => $count,
  298. 'tasks_preview' => $parsed
  299. ]);
  300. }
  301. /**
  302. * 清空队列并删除队列日志记录
  303. */
  304. public function stopQueueProcesses()
  305. {
  306. Db::name('image_task_log')
  307. ->where('log', '队列中')
  308. ->whereOr('status', 1)
  309. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  310. ->update([
  311. 'status' => "-1",
  312. 'log' => '清空取消队列',
  313. 'mod_rq' => date('Y-m-d H:i:s')
  314. ]);
  315. Db::name('image_task_log')
  316. ->whereLike('log', '%处理中%')
  317. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  318. ->update([
  319. 'status' => "-1",
  320. 'log' => '清空取消队列',
  321. 'mod_rq' => date('Y-m-d H:i:s')
  322. ]);
  323. $redis = new \Redis();
  324. $redis->connect('127.0.0.1', 6379);
  325. $redis->auth('123456');
  326. $redis->select(15);
  327. $key_txttoimg = 'queues:txttoimg:reserved';
  328. $key_txttotxt = 'queues:txttotxt:reserved';
  329. $key_imgtotxt = 'queues:imgtotxt:reserved';
  330. $key_imgtoimg = 'queues:imgtoimg:reserved';
  331. // 清空 Redis 队列
  332. $redis->del($key_txttoimg);
  333. $redis->del($key_txttotxt);
  334. $redis->del($key_imgtotxt);
  335. $redis->del($key_imgtoimg);
  336. $count = $redis->lLen($key_txttoimg) + $redis->lLen($key_txttotxt) + $redis->lLen($key_imgtotxt) + $redis->lLen($key_imgtoimg);
  337. // if ($count === 0) {
  338. // return json([
  339. // 'code' => 1,
  340. // 'msg' => '暂无队列需要停止'
  341. // ]);
  342. // }
  343. return json([
  344. 'code' => 0,
  345. 'msg' => '成功停止队列任务'
  346. ]);
  347. }
  348. /**
  349. * 开启队列任务
  350. * 暂时用不到、服务器已开启自动开启队列模式
  351. */
  352. // public function kaiStats()
  353. // {
  354. // // 判断是否已有监听进程在运行
  355. // $check = shell_exec("ps aux | grep 'queue:listen' | grep -v grep");
  356. // if ($check) {
  357. // return json([
  358. // 'code' => 1,
  359. // 'msg' => '监听进程已存在,请勿重复启动'
  360. // ]);
  361. // }
  362. // // 启动监听
  363. // $command = 'nohup php think queue:listen --queue --timeout=300 --sleep=3 --memory=256 > /var/log/job_queue.log 2>&1 &';
  364. // exec($command, $output, $status);
  365. // if ($status === 0) {
  366. // return json([
  367. // 'code' => 0,
  368. // 'msg' => '队列监听已启动'
  369. // ]);
  370. // } else {
  371. // return json([
  372. // 'code' => 1,
  373. // 'msg' => '队列启动失败',
  374. // 'output' => $output
  375. // ]);
  376. // }
  377. // }
  378. /**
  379. * 通过店铺ID-查询对应店铺表数据
  380. *
  381. */
  382. public function PatternApi()
  383. {
  384. $params = $this->request->param('pattern_id', '');
  385. $tableName = 'pattern-' . $params;
  386. // 连接 MongoDB
  387. $mongo = Db::connect('mongodb');
  388. // 查询指定 skc 的数据
  389. $data = $mongo->table($tableName)
  390. ->field('
  391. name,
  392. skc,
  393. file
  394. ')
  395. ->where("skc", '0853004152036')
  396. ->select();
  397. $data = json_decode(json_encode($data), true); // 数组
  398. return json([
  399. 'code' => 0,
  400. 'msg' => '获取成功',
  401. 'data' => $data
  402. ]);
  403. }
  404. }