WorkOrder.php 14 KB

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