WorkOrder.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. public function imgtowimg()
  27. {
  28. $prompt = $this->request->param('prompt', '');
  29. $denoising = (float)$this->request->param('denoising_strength', 0.2);//重绘幅度
  30. $modelName = $this->request->param('model', 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]');
  31. $scale = (float)$this->request->param('scale', 2.0); // 放大倍数
  32. $imgRelPath = 'uploads/operate/ai/Preview/arr/0828004096727.png';
  33. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  34. if (!file_exists($imgPath)) {
  35. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  36. }
  37. // 编码原图为 base64
  38. $imgData = file_get_contents($imgPath);
  39. $base64Img = base64_encode($imgData);
  40. $initImage = 'data:image/png;base64,' . $base64Img;
  41. // 构造接口请求数据(宽高不传)
  42. $postData = json_encode([
  43. 'prompt' => $prompt,
  44. 'steps' => 30,
  45. 'cfg_scale' => 7,
  46. 'inpainting_fill' => 1,
  47. 'denoising_strength' => $denoising,
  48. 'init_images' => [$initImage],
  49. 'override_settings' => [
  50. 'sd_model_checkpoint' => $modelName
  51. ]
  52. ]);
  53. // 发送 POST 请求到 img2img 接口
  54. $apiUrl = "http://20.0.17.233:45001/sdapi/v1/img2img";
  55. $headers = ['Content-Type: application/json'];
  56. $ch = curl_init();
  57. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  58. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  59. curl_setopt($ch, CURLOPT_POST, true);
  60. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  61. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  62. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  63. $response = curl_exec($ch);
  64. $error = curl_error($ch);
  65. curl_close($ch);
  66. if ($error) {
  67. return json(['code' => 1, 'msg' => '请求失败:' . $error]);
  68. }
  69. $data = json_decode($response, true);
  70. if (!isset($data['images'][0])) {
  71. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  72. }
  73. // 保存原始生成图
  74. $resultImg = base64_decode($data['images'][0]);
  75. $saveDir = ROOT_PATH . 'public/uploads/img/';
  76. if (!is_dir($saveDir)) {
  77. mkdir($saveDir, 0755, true);
  78. }
  79. $fileName = 'img2img_' . date('Ymd_His') . '_' . mt_rand(1000, 9999) . '.png';
  80. $savePath = $saveDir . $fileName;
  81. file_put_contents($savePath, $resultImg);
  82. // 放大图像处理
  83. $imageResource = imagecreatefromstring($resultImg);
  84. if ($imageResource && $scale > 1) {
  85. $originW = imagesx($imageResource);
  86. $originH = imagesy($imageResource);
  87. $scaledW = (int)($originW * $scale);
  88. $scaledH = (int)($originH * $scale);
  89. $resized = imagecreatetruecolor($scaledW, $scaledH);
  90. imagecopyresampled($resized, $imageResource, 0, 0, 0, 0, $scaledW, $scaledH, $originW, $originH);
  91. $zoomName = 'img2img_zoom_' . date('Ymd_His') . '_' . mt_rand(1000, 9999) . '.png';
  92. $zoomPath = $saveDir . $zoomName;
  93. imagepng($resized, $zoomPath);
  94. imagedestroy($resized);
  95. imagedestroy($imageResource);
  96. return json([
  97. 'code' => 0,
  98. 'msg' => '生成并放大成功',
  99. 'data' => [
  100. // 'origin_url' => '/uploads/img/' . $fileName,
  101. 'zoom_url' => '/uploads/img/' . $zoomName
  102. ]
  103. ]);
  104. }
  105. return json([
  106. 'code' => 0,
  107. 'msg' => '生成成功(未放大)',
  108. 'data' => [
  109. 'origin_url' => '/uploads/img/' . $fileName
  110. ]
  111. ]);
  112. }
  113. /**
  114. * 查询队列列表
  115. * 统计文件对应的队列情况
  116. */
  117. public function get_queue_logs()
  118. {
  119. $params = $this->request->param('old_image_file', '');
  120. $queue_logs = Db::name('queue_logs')
  121. ->where('old_image_file', $params)
  122. ->order('id desc')
  123. ->select();
  124. foreach ($queue_logs as &$log) {
  125. $taskId = $log['id'];
  126. // 从 image_task_log 表统计状态
  127. $statusCount = Db::name('image_task_log')
  128. ->field('status, COUNT(*) as count')
  129. ->where('task_id', $taskId)
  130. ->where('mod_rq', null)
  131. ->group('status')
  132. ->select();
  133. $log['已完成数量'] = 0;
  134. $log['处理中数量'] = 0;
  135. $log['排队中的数量'] = 0;
  136. $log['失败数量'] = 0;
  137. foreach ($statusCount as $item) {
  138. switch ($item['status']) {
  139. case 0:
  140. $log['排队中的数量'] = $item['count'];
  141. break;
  142. case 1:
  143. $log['处理中数量'] = $item['count'];
  144. break;
  145. case 2:
  146. $log['已完成数量'] = $item['count'];
  147. break;
  148. case -1:
  149. $log['失败数量'] = $item['count'];
  150. break;
  151. }
  152. }
  153. // ✅ 只保留排队中的数量大于 0 的记录
  154. if ($log['排队中的数量'] > 0) {
  155. $result[] = $log;
  156. }
  157. }
  158. return json([
  159. 'code' => 0,
  160. 'msg' => '查询成功',
  161. 'data' => $result,
  162. 'count' => count($result)
  163. ]);
  164. }
  165. /**
  166. * 查询总队列状态(统计当前处理的数据量)
  167. */
  168. public function queueStats()
  169. {
  170. $statusList = Db::name('image_task_log')
  171. ->field('status, COUNT(*) as total')
  172. ->where('mod_rq', null)
  173. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  174. ->group('status')
  175. ->select();
  176. $statusCount = [];
  177. foreach ($statusList as $item) {
  178. $statusCount[$item['status']] = $item['total'];
  179. }
  180. // 总数为所有状态和
  181. $total = array_sum($statusCount);
  182. //获取队列当前状态
  183. $statusText = Db::name('queue_logs')->order('id desc')->value('status');
  184. return json([
  185. 'code' => 0,
  186. 'msg' => '获取成功',
  187. 'data' => [
  188. '总任务数' => $total,
  189. '待处理' => $statusCount[0] ?? 0,
  190. '处理中' => $statusCount[1] ?? 0,
  191. '成功' => $statusCount[2] ?? 0,
  192. '失败' => $statusCount[-1] ?? 0,
  193. '当前状态' => $statusText
  194. ]
  195. ]);
  196. }
  197. /**
  198. * 显示当前运行中的队列监听进程
  199. */
  200. public function viewQueueStatus()
  201. {
  202. $redis = new \Redis();
  203. $redis->connect('127.0.0.1', 6379);
  204. $redis->auth('123456');
  205. $redis->select(15);
  206. $key = 'queues:imgtotxt';
  207. // 判断 key 是否存在,避免报错
  208. if (!$redis->exists($key)) {
  209. return json([
  210. 'code' => 0,
  211. 'msg' => '查询成功,队列为空',
  212. 'count' => 0,
  213. 'tasks_preview' => []
  214. ]);
  215. }
  216. $count = $redis->lLen($key);
  217. $list = $redis->lRange($key, 0, 9);
  218. // 解码 JSON 内容,确保每一项都有效
  219. $parsed = array_filter(array_map(function ($item) {
  220. return json_decode($item, true);
  221. }, $list), function ($item) {
  222. return !is_null($item);
  223. });
  224. return json([
  225. 'code' => 0,
  226. 'msg' => '查询成功',
  227. 'count' => $count,
  228. 'tasks_preview' => $parsed
  229. ]);
  230. }
  231. /**
  232. * 清空队列并删除队列日志记录
  233. */
  234. public function stopQueueProcesses()
  235. {
  236. $redis = new \Redis();
  237. $redis->connect('127.0.0.1', 6379);
  238. $redis->auth('123456');
  239. $redis->select(15);
  240. $key_txttoimg = 'queues:txttoimg';
  241. $key_txttotxt = 'queues:txttotxt';
  242. $key_imgtotxt = 'queues:imgtotxt';
  243. $count = $redis->lLen($key_txttoimg) + $redis->lLen($key_txttotxt) + $redis->lLen($key_imgtotxt);
  244. if ($count === 0) {
  245. return json([
  246. 'code' => 1,
  247. 'msg' => '暂无队列需要停止'
  248. ]);
  249. }
  250. // 清空 Redis 队列
  251. $redis->del($key_txttoimg);
  252. $redis->del($key_txttotxt);
  253. $redis->del($key_imgtotxt);
  254. // 删除数据库中 log = '队列中' 的记录
  255. Db::name('image_task_log')
  256. ->where('log', '队列中')
  257. ->update([
  258. 'status' => "",
  259. 'log' => '清空取消队列',
  260. 'mod_rq' => date('Y-m-d H:i:s')
  261. ]);
  262. return json([
  263. 'code' => 0,
  264. 'msg' => '已成功停止队列任务并清除队列日志'
  265. ]);
  266. }
  267. /**
  268. * 开启队列任务
  269. * 暂时用不到、服务器已开启自动开启队列模式
  270. */
  271. // public function kaiStats()
  272. // {
  273. // // 判断是否已有监听进程在运行
  274. // $check = shell_exec("ps aux | grep 'queue:listen' | grep -v grep");
  275. // if ($check) {
  276. // return json([
  277. // 'code' => 1,
  278. // 'msg' => '监听进程已存在,请勿重复启动'
  279. // ]);
  280. // }
  281. // // 启动监听
  282. // $command = 'nohup php think queue:listen --queue --timeout=300 --sleep=3 --memory=256 > /var/log/job_queue.log 2>&1 &';
  283. // exec($command, $output, $status);
  284. // if ($status === 0) {
  285. // return json([
  286. // 'code' => 0,
  287. // 'msg' => '队列监听已启动'
  288. // ]);
  289. // } else {
  290. // return json([
  291. // 'code' => 1,
  292. // 'msg' => '队列启动失败',
  293. // 'output' => $output
  294. // ]);
  295. // }
  296. // }
  297. }