WorkOrder.php 15 KB

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