WorkOrder.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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. $imgRelPath = 'uploads/operate/ai/Preview/arr/一朵盛开的白色牡丹花为主体采用厚涂技法花心和背景点缀金箔灰银.png';
  34. $imgPath = ROOT_PATH . 'public/' . $imgRelPath;
  35. //原图是否存在
  36. if (!file_exists($imgPath)) {
  37. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  38. }
  39. // -------- 图像编码 -------- //
  40. $imgData = file_get_contents($imgPath);
  41. $base64Img = base64_encode($imgData);
  42. $initImage = 'data:image/png;base64,' . $base64Img;
  43. // -------- 请求体构建 -------- //
  44. $postData = json_encode([
  45. 'prompt' => $prompt,
  46. 'steps' => 30, // 步数
  47. 'cfg_scale' => 7, // CFG 强度
  48. 'denoising_strength' => 0.2, // 重绘强度
  49. 'width' => 679, // 图像宽度
  50. 'height' => 862, // 图像高度
  51. 'resize_mode' => 1, // 保留原图比例并裁剪
  52. 'inpaint_full_res' => true, // 使用原图分辨率
  53. 'inpaint_full_res_padding' => 64, // 边缘补全像素
  54. 'mask_blur' => 4, // 蒙版柔化
  55. 'inpainting_fill' => 3, // 自动填充内容(不是黑色)
  56. 'sampler_name' => 'DPM++ 2M SDE', // 采样器
  57. 'scheduler' => 'Exponential', // ✅ 调度类型(补充字段)
  58. 'seed' => 3689437019, // 固定种子(确保结果可复现)
  59. 'init_images' => [$initImage], // 原图 base64
  60. 'override_settings' => [
  61. 'sd_model_checkpoint' => 'AbyssOrangeMix2_sfw', // 模型名
  62. 'sd_vae' => "Automatic",
  63. 'CLIP_stop_at_last_layers' => 2
  64. ],
  65. 'override_settings_restore_afterwards' => true
  66. ]);
  67. // -------- 发送请求到 SD API -------- //
  68. $apiUrl = "http://20.0.17.188:45001/sdapi/v1/img2img";
  69. $headers = ['Content-Type: application/json'];
  70. $ch = curl_init();
  71. curl_setopt($ch, CURLOPT_URL, $apiUrl);
  72. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  73. curl_setopt($ch, CURLOPT_POST, true);
  74. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  75. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  76. curl_setopt($ch, CURLOPT_TIMEOUT, 90);
  77. $response = curl_exec($ch);
  78. $error = curl_error($ch);
  79. curl_close($ch);
  80. if ($error) {return json(['code' => 1, 'msg' => '请求失败:' . $error]);}
  81. $data = json_decode($response, true);
  82. if (!isset($data['images'][0])) {
  83. return json(['code' => 1, 'msg' => '接口未返回图像数据']);
  84. }
  85. // -------- 保存生成图像 -------- //
  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 . '-' . time() . '-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. // 配置参数
  110. $config = [
  111. 'input_dir' => 'uploads/operate/ai/Preview/arr/',
  112. 'output_dir' => 'uploads/extra_image/',
  113. 'api_url' => 'http://20.0.17.188:45001/sdapi/v1/extra-single-image',
  114. 'timeout' => 120, // 增加超时时间,高清处理可能耗时较长
  115. 'upscale_params' => [
  116. 'resize_mode' => 0,
  117. 'show_extras_results' => true,
  118. 'gfpgan_visibility' => 0, // 人脸修复关闭
  119. 'codeformer_visibility' => 0, // 人脸修复关闭
  120. 'codeformer_weight' => 0,
  121. 'upscaling_resize' => 2.45, // 放大倍数
  122. 'upscaling_crop' => true,
  123. 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B', // 主放大模型
  124. 'upscaler_2' => 'None', // 不使用第二放大器
  125. 'extras_upscaler_2_visibility' => 0,
  126. 'upscale_first' => false,
  127. ]
  128. ];
  129. // 输入文件处理
  130. $imgRelPath = '图案的整体色调是柔和的蓝色和灰色形成温馨而宁静的视觉效果花卉.png';
  131. $imgPath = ROOT_PATH . 'public/' . $config['input_dir'] . $imgRelPath;
  132. if (!file_exists($imgPath)) {
  133. return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]);
  134. }
  135. // 读取并编码图片
  136. try {
  137. $imgData = file_get_contents($imgPath);
  138. if ($imgData === false) {
  139. throw new Exception('无法读取图片文件');
  140. }
  141. $base64Img = base64_encode($imgData);
  142. } catch (Exception $e) {
  143. return json(['code' => 1, 'msg' => '图片处理失败:' . $e->getMessage()]);
  144. }
  145. // 准备API请求数据
  146. $postData = array_merge($config['upscale_params'], ['image' => $base64Img]);
  147. $jsonData = json_encode($postData);
  148. if ($jsonData === false) {
  149. return json(['code' => 1, 'msg' => 'JSON编码失败']);
  150. }
  151. // 调用API进行高清放大
  152. $ch = curl_init();
  153. curl_setopt_array($ch, [
  154. CURLOPT_URL => $config['api_url'],
  155. CURLOPT_RETURNTRANSFER => true,
  156. CURLOPT_POST => true,
  157. CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
  158. CURLOPT_POSTFIELDS => $jsonData,
  159. CURLOPT_TIMEOUT => $config['timeout'],
  160. CURLOPT_CONNECTTIMEOUT => 30,
  161. ]);
  162. $response = curl_exec($ch);
  163. $error = curl_error($ch);
  164. $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  165. curl_close($ch);
  166. if ($error) {
  167. return json(['code' => 1, 'msg' => 'API请求失败:' . $error]);
  168. }
  169. if ($httpCode !== 200) {
  170. return json(['code' => 1, 'msg' => 'API返回错误状态码:' . $httpCode]);
  171. }
  172. $data = json_decode($response, true);
  173. if (json_last_error() !== JSON_ERROR_NONE) {
  174. return json(['code' => 1, 'msg' => 'API返回数据解析失败']);
  175. }
  176. if (!isset($data['image']) || empty($data['image'])) {
  177. return json(['code' => 1, 'msg' => '接口未返回有效的图像数据']);
  178. }
  179. // 保存处理后的图片
  180. try {
  181. $resultImg = base64_decode($data['image']);
  182. if ($resultImg === false) {
  183. throw new Exception('Base64解码失败');
  184. }
  185. $saveDir = ROOT_PATH . 'public/' . $config['output_dir'];
  186. if (!is_dir($saveDir) && !mkdir($saveDir, 0755, true)) {
  187. throw new Exception('无法创建输出目录');
  188. }
  189. $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME);
  190. $fileName = $originalBaseName . '-hd.png'; // 使用-hd后缀更明确
  191. $savePath = $saveDir . $fileName;
  192. if (file_put_contents($savePath, $resultImg) === false) {
  193. throw new Exception('无法保存处理后的图片');
  194. }
  195. // 返回成功响应
  196. return json([
  197. 'code' => 0,
  198. 'msg' => '图像高清放大处理成功',
  199. 'data' => [
  200. 'url' => '/' . $config['output_dir'] . $fileName,
  201. 'original_size' => filesize($imgPath),
  202. 'processed_size' => filesize($savePath),
  203. 'resolution' => getimagesize($savePath), // 返回新图片的分辨率
  204. ]
  205. ]);
  206. } catch (Exception $e) {
  207. return json(['code' => 1, 'msg' => '保存结果失败:' . $e->getMessage()]);
  208. }
  209. }
  210. /**
  211. * 获取 SD 模型列表
  212. * 接口地址: /sdapi/v1/sd-models
  213. */
  214. public function sd_models() {
  215. $url = "http://20.0.17.188:45001/sdapi/v1/sd-models";
  216. // 初始化 cURL
  217. $ch = curl_init();
  218. // 设置请求参数
  219. curl_setopt($ch, CURLOPT_URL, $url);
  220. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  221. curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  222. curl_setopt($ch, CURLOPT_HTTPHEADER, [
  223. 'Content-Type: application/json',
  224. 'Accept: application/json',
  225. ]);
  226. // 发送请求
  227. $response = curl_exec($ch);
  228. // 错误处理
  229. if (curl_errno($ch)) {
  230. curl_close($ch);
  231. return json([
  232. 'code' => 1,
  233. 'msg' => '请求失败: ' . curl_error($ch),
  234. 'data' => [],
  235. 'count' => 0
  236. ]);
  237. }
  238. curl_close($ch);
  239. // 解析 JSON 响应
  240. $result = json_decode($response, true);
  241. // 判断返回数据是否有效
  242. if (!is_array($result)) {
  243. return json([
  244. 'code' => 1,
  245. 'msg' => '数据解析失败',
  246. 'data' => [],
  247. 'count' => 0
  248. ]);
  249. }
  250. // 正常返回
  251. return json([
  252. 'code' => 0,
  253. 'msg' => '查询成功',
  254. 'data' => $result,
  255. 'count' => count($result)
  256. ]);
  257. }
  258. /**
  259. * 查询队列列表
  260. * 统计文件对应的队列情况
  261. */
  262. public function get_queue_logs()
  263. {
  264. $params = $this->request->param('old_image_file', '');
  265. $queue_logs = Db::name('queue_logs')
  266. ->where('old_image_file', $params)
  267. ->order('id desc')
  268. ->select();
  269. $result = []; //初始化变量,避免未定义错误
  270. foreach ($queue_logs as &$log) {
  271. $taskId = $log['id'];
  272. $statusCount = Db::name('image_task_log')
  273. ->field('status, COUNT(*) as count')
  274. ->where('task_id', $taskId)
  275. ->where('mod_rq', null)
  276. ->group('status')
  277. ->select();
  278. $log['已完成数量'] = 0;
  279. $log['处理中数量'] = 0;
  280. $log['排队中的数量'] = 0;
  281. $log['失败数量'] = 0;
  282. foreach ($statusCount as $item) {
  283. switch ($item['status']) {
  284. case 0:
  285. $log['排队中的数量'] = $item['count'];
  286. break;
  287. case 1:
  288. $log['处理中数量'] = $item['count'];
  289. break;
  290. case 2:
  291. $log['已完成数量'] = $item['count'];
  292. break;
  293. case -1:
  294. $log['失败数量'] = $item['count'];
  295. break;
  296. }
  297. }
  298. // if ($log['排队中的数量'] >$log['已完成数量']) {
  299. // $result[] = $log;
  300. // }
  301. if ($log['排队中的数量']) {
  302. $result[] = $log;
  303. }
  304. // if ($log['处理中数量'] >= 0) {
  305. // $result[] = $log;
  306. // }
  307. }
  308. return json([
  309. 'code' => 0,
  310. 'msg' => '查询成功',
  311. 'data' => $result,
  312. 'count' => count($result)
  313. ]);
  314. }
  315. /**
  316. * 查询总队列状态(统计当前处理的数据量)
  317. */
  318. public function queueStats()
  319. {
  320. $statusList = Db::name('image_task_log')
  321. ->field('status, COUNT(*) as total')
  322. ->where('mod_rq', null)
  323. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  324. ->group('status')
  325. ->select();
  326. $statusCount = [];
  327. foreach ($statusList as $item) {
  328. $statusCount[$item['status']] = $item['total'];
  329. }
  330. // 总数为所有状态和
  331. $total = array_sum($statusCount);
  332. //获取队列当前状态
  333. $statusText = Db::name('queue_logs')->order('id desc')->value('status');
  334. return json([
  335. 'code' => 0,
  336. 'msg' => '获取成功',
  337. 'data' => [
  338. '总任务数' => $total,
  339. '待处理' => $statusCount[0] ?? 0,
  340. '处理中' => $statusCount[1] ?? 0,
  341. '成功' => $statusCount[2] ?? 0,
  342. '失败' => $statusCount[-1] ?? 0,
  343. '当前状态' => $statusText
  344. ]
  345. ]);
  346. }
  347. /**
  348. * 显示当前运行中的队列监听进程
  349. */
  350. public function viewQueueStatus()
  351. {
  352. $redis = new \Redis();
  353. $redis->connect('127.0.0.1', 6379);
  354. $redis->auth('123456');
  355. $redis->select(15);
  356. $key = 'queues:imgtotxt';
  357. // 判断 key 是否存在,避免报错
  358. if (!$redis->exists($key)) {
  359. return json([
  360. 'code' => 0,
  361. 'msg' => '查询成功,队列为空',
  362. 'count' => 0,
  363. 'tasks_preview' => []
  364. ]);
  365. }
  366. $count = $redis->lLen($key);
  367. $list = $redis->lRange($key, 0, 9);
  368. // 解码 JSON 内容,确保每一项都有效
  369. $parsed = array_filter(array_map(function ($item) {
  370. return json_decode($item, true);
  371. }, $list), function ($item) {
  372. return !is_null($item);
  373. });
  374. return json([
  375. 'code' => 0,
  376. 'msg' => '查询成功',
  377. 'count' => $count,
  378. 'tasks_preview' => $parsed
  379. ]);
  380. }
  381. /**
  382. * 清空队列并删除队列日志记录
  383. */
  384. public function stopQueueProcesses()
  385. {
  386. Db::name('image_task_log')
  387. ->where('log', '队列中')
  388. ->whereOr('status', 1)
  389. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  390. ->update([
  391. 'status' => "-1",
  392. 'log' => '清空取消队列',
  393. 'mod_rq' => date('Y-m-d H:i:s')
  394. ]);
  395. Db::name('image_task_log')
  396. ->whereLike('log', '%处理中%')
  397. ->where('create_time', '>=', date('Y-m-d 00:00:00'))
  398. ->update([
  399. 'status' => "-1",
  400. 'log' => '清空取消队列',
  401. 'mod_rq' => date('Y-m-d H:i:s')
  402. ]);
  403. $redis = new \Redis();
  404. $redis->connect('127.0.0.1', 6379);
  405. $redis->auth('123456');
  406. $redis->select(15);
  407. $key_txttoimg = 'queues:txttoimg:reserved';
  408. $key_txttotxt = 'queues:txttotxt:reserved';
  409. $key_imgtotxt = 'queues:imgtotxt:reserved';
  410. $key_imgtoimg = 'queues:imgtoimg:reserved';
  411. // 清空 Redis 队列
  412. $redis->del($key_txttoimg);
  413. $redis->del($key_txttotxt);
  414. $redis->del($key_imgtotxt);
  415. $redis->del($key_imgtoimg);
  416. $count = $redis->lLen($key_txttoimg) + $redis->lLen($key_txttotxt) + $redis->lLen($key_imgtotxt) + $redis->lLen($key_imgtoimg);
  417. // if ($count === 0) {
  418. // return json([
  419. // 'code' => 1,
  420. // 'msg' => '暂无队列需要停止'
  421. // ]);
  422. // }
  423. return json([
  424. 'code' => 0,
  425. 'msg' => '成功停止队列任务'
  426. ]);
  427. }
  428. /**
  429. * 开启队列任务
  430. * 暂时用不到、服务器已开启自动开启队列模式
  431. */
  432. // public function kaiStats()
  433. // {
  434. // // 判断是否已有监听进程在运行
  435. // $check = shell_exec("ps aux | grep 'queue:listen' | grep -v grep");
  436. // if ($check) {
  437. // return json([
  438. // 'code' => 1,
  439. // 'msg' => '监听进程已存在,请勿重复启动'
  440. // ]);
  441. // }
  442. // // 启动监听
  443. // $command = 'nohup php think queue:listen --queue --timeout=300 --sleep=3 --memory=256 > /var/log/job_queue.log 2>&1 &';
  444. // exec($command, $output, $status);
  445. // if ($status === 0) {
  446. // return json([
  447. // 'code' => 0,
  448. // 'msg' => '队列监听已启动'
  449. // ]);
  450. // } else {
  451. // return json([
  452. // 'code' => 1,
  453. // 'msg' => '队列启动失败',
  454. // 'output' => $output
  455. // ]);
  456. // }
  457. // }
  458. /**
  459. * 通过店铺ID-查询对应店铺表数据
  460. *
  461. */
  462. public function PatternApi()
  463. {
  464. $params = $this->request->param('pattern_id', '');
  465. $tableName = 'pattern-' . $params;
  466. // 连接 MongoDB
  467. $mongo = Db::connect('mongodb');
  468. // 查询指定 skc 的数据
  469. $data = $mongo->table($tableName)
  470. ->field('
  471. name,
  472. skc,
  473. file
  474. ')
  475. ->where("skc", '0853004152036')
  476. ->select();
  477. $data = json_decode(json_encode($data), true); // 数组
  478. return json([
  479. 'code' => 0,
  480. 'msg' => '获取成功',
  481. 'data' => $data
  482. ]);
  483. }
  484. }