request->param(); $service = new ImageService(); $service->handleImage($params); $this->success('任务成功提交至队列'); } /** * 图生图功能 - /sdapi/v1/img2img */ public function imgtowimg() { $prompt = $this->request->param('prompt', ''); // 用户传入的提示词 // $imgRelPath = 'uploads/operate/ai/Preview/arr/0828004096727.png'; // $imgRelPath = 'uploads/operate/ai/Preview/arr/1751019283787.jpg'; $imgRelPath = 'uploads/operate/ai/Preview/arr/圣诞主题复古油画圣诞老人和孩子们乘坐红色雪橇在雪地中滑行充满.png'; $imgPath = ROOT_PATH . 'public/' . $imgRelPath; // 校验原图是否存在 if (!file_exists($imgPath)) { return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]); } // -------- 参数调整 -------- // $denoising = 0.2; // 降低重绘幅度 $steps = 10; // 减少迭代步数 $cfgScale = 7; $clipSkip = 7; $sampler = 'DPM++ 2M SDE Heun'; $modelName = 'realisticVisionV51_v51VAE-inpainting.safetensors [f0d4872d24]'; $vaeName = 'anything-v4.5.vae.pt'; $seed = 611075477; $targetW = 679; // 目标宽度 $targetH = 862; // 目标高度 // -------- 图像编码 -------- // $imgData = file_get_contents($imgPath); $base64Img = base64_encode($imgData); $initImage = 'data:image/png;base64,' . $base64Img; // -------- 请求体构建 -------- // $postData = json_encode([ 'prompt' => $prompt, 'steps' => $steps, 'cfg_scale' => $cfgScale, 'denoising_strength' => $denoising, 'width' => $targetW, 'height' => $targetH, 'resize_mode' => 0, 'sampler_name' => $sampler, 'seed' => $seed, 'inpaint_full_res' => true, // 关闭高分辨率重绘 'inpainting_fill' => 1, 'init_images' => [$initImage], 'override_settings' => [ 'sd_model_checkpoint' => $modelName, 'sd_vae' => $vaeName, 'CLIP_stop_at_last_layers' => $clipSkip ], 'override_settings_restore_afterwards' => true ]); // -------- 发送请求到 SD API -------- // $apiUrl = "http://20.0.17.233:45001/sdapi/v1/img2img"; $headers = ['Content-Type: application/json']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_TIMEOUT, 90); $response = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) { return json(['code' => 1, 'msg' => '请求失败:' . $error]); } $data = json_decode($response, true); if (!isset($data['images'][0])) { return json(['code' => 1, 'msg' => '接口未返回图像数据']); } // -------- 保存生成图像 -------- // $resultImg = base64_decode($data['images'][0]); $saveDir = ROOT_PATH . 'public/uploads/img2img/'; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME); $fileName = $originalBaseName . '-' . time() . '-1.png'; $savePath = $saveDir . $fileName; file_put_contents($savePath, $resultImg); return json([ 'code' => 0, 'msg' => '图像生成成功', 'data' => [ 'origin_url' => '/uploads/img2img/' . $fileName ] ]); } /** * 后期图像处理 * /sdapi/v1/extra-single-image */ public function extra_image() { $imgRelPath = 'uploads/img2img/0828004096727-1.png'; // 图生图结果 $imgPath = ROOT_PATH . 'public/' . $imgRelPath; if (!file_exists($imgPath)) { return json(['code' => 1, 'msg' => '原图不存在:' . $imgRelPath]); } $imgData = file_get_contents($imgPath); $base64Img = base64_encode($imgData); $postData = json_encode([ 'resize_mode' => 0, 'show_extras_results' => true, 'gfpgan_visibility' => 0, 'codeformer_visibility' => 0, 'codeformer_weight' => 0, 'upscaling_resize' => 2.45, 'upscaling_crop' => true, 'upscaler_1' => 'R-ESRGAN 4x+ Anime6B', 'upscaler_2' => 'None', 'extras_upscaler_2_visibility' => 0, 'upscale_first' => false, 'image' => $base64Img ]); $apiUrl = "http://20.0.17.233:45001/sdapi/v1/extra-single-image"; $headers = ['Content-Type: application/json']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $apiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_TIMEOUT, 90); $response = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) { return json(['code' => 1, 'msg' => '请求失败:' . $error]); } $data = json_decode($response, true); if (!isset($data['image'])) { return json(['code' => 1, 'msg' => '接口未返回图像数据']); } // 保存:原图基础名 + -2 $resultImg = base64_decode($data['image']); $saveDir = ROOT_PATH . 'public/uploads/extra_image/'; if (!is_dir($saveDir)) { mkdir($saveDir, 0755, true); } $originalBaseName = pathinfo($imgRelPath, PATHINFO_FILENAME); $fileName = $originalBaseName . '-2.png'; $savePath = $saveDir . $fileName; file_put_contents($savePath, $resultImg); return json([ 'code' => 0, 'msg' => '图像后处理成功', 'data' => [ 'url' => '/uploads/extra_image/' . $fileName ] ]); } /** * 查询队列列表 * 统计文件对应的队列情况 */ public function get_queue_logs() { $params = $this->request->param('old_image_file', ''); $queue_logs = Db::name('queue_logs') ->where('old_image_file', $params) ->order('id desc') ->select(); $result = []; //初始化变量,避免未定义错误 foreach ($queue_logs as &$log) { $taskId = $log['id']; $statusCount = Db::name('image_task_log') ->field('status, COUNT(*) as count') ->where('task_id', $taskId) ->where('mod_rq', null) ->group('status') ->select(); $log['已完成数量'] = 0; $log['处理中数量'] = 0; $log['排队中的数量'] = 0; $log['失败数量'] = 0; foreach ($statusCount as $item) { switch ($item['status']) { case 0: $log['排队中的数量'] = $item['count']; break; case 1: $log['处理中数量'] = $item['count']; break; case 2: $log['已完成数量'] = $item['count']; break; case -1: $log['失败数量'] = $item['count']; break; } } // if ($log['排队中的数量'] >$log['已完成数量']) { // $result[] = $log; // } if ($log['排队中的数量']) { $result[] = $log; } // if ($log['处理中数量'] >= 0) { // $result[] = $log; // } } return json([ 'code' => 0, 'msg' => '查询成功', 'data' => $result, 'count' => count($result) ]); } /** * 查询总队列状态(统计当前处理的数据量) */ public function queueStats() { $statusList = Db::name('image_task_log') ->field('status, COUNT(*) as total') ->where('mod_rq', null) ->where('create_time', '>=', date('Y-m-d 00:00:00')) ->group('status') ->select(); $statusCount = []; foreach ($statusList as $item) { $statusCount[$item['status']] = $item['total']; } // 总数为所有状态和 $total = array_sum($statusCount); //获取队列当前状态 $statusText = Db::name('queue_logs')->order('id desc')->value('status'); return json([ 'code' => 0, 'msg' => '获取成功', 'data' => [ '总任务数' => $total, '待处理' => $statusCount[0] ?? 0, '处理中' => $statusCount[1] ?? 0, '成功' => $statusCount[2] ?? 0, '失败' => $statusCount[-1] ?? 0, '当前状态' => $statusText ] ]); } /** * 显示当前运行中的队列监听进程 */ public function viewQueueStatus() { $redis = new \Redis(); $redis->connect('127.0.0.1', 6379); $redis->auth('123456'); $redis->select(15); $key = 'queues:imgtotxt'; // 判断 key 是否存在,避免报错 if (!$redis->exists($key)) { return json([ 'code' => 0, 'msg' => '查询成功,队列为空', 'count' => 0, 'tasks_preview' => [] ]); } $count = $redis->lLen($key); $list = $redis->lRange($key, 0, 9); // 解码 JSON 内容,确保每一项都有效 $parsed = array_filter(array_map(function ($item) { return json_decode($item, true); }, $list), function ($item) { return !is_null($item); }); return json([ 'code' => 0, 'msg' => '查询成功', 'count' => $count, 'tasks_preview' => $parsed ]); } /** * 清空队列并删除队列日志记录 */ public function stopQueueProcesses() { Db::name('image_task_log') ->where('log', '队列中') ->whereOr('status', 1) ->where('create_time', '>=', date('Y-m-d 00:00:00')) ->update([ 'status' => "-1", 'log' => '清空取消队列', 'mod_rq' => date('Y-m-d H:i:s') ]); Db::name('image_task_log') ->whereLike('log', '%处理中%') ->where('create_time', '>=', date('Y-m-d 00:00:00')) ->update([ 'status' => "-1", 'log' => '清空取消队列', 'mod_rq' => date('Y-m-d H:i:s') ]); $redis = new \Redis(); $redis->connect('127.0.0.1', 6379); $redis->auth('123456'); $redis->select(15); $key_txttoimg = 'queues:txttoimg:reserved'; $key_txttotxt = 'queues:txttotxt:reserved'; $key_imgtotxt = 'queues:imgtotxt:reserved'; $key_imgtoimg = 'queues:imgtoimg:reserved'; // 清空 Redis 队列 $redis->del($key_txttoimg); $redis->del($key_txttotxt); $redis->del($key_imgtotxt); $redis->del($key_imgtoimg); $count = $redis->lLen($key_txttoimg) + $redis->lLen($key_txttotxt) + $redis->lLen($key_imgtotxt) + $redis->lLen($key_imgtoimg); // if ($count === 0) { // return json([ // 'code' => 1, // 'msg' => '暂无队列需要停止' // ]); // } return json([ 'code' => 0, 'msg' => '成功停止队列任务' ]); } /** * 开启队列任务 * 暂时用不到、服务器已开启自动开启队列模式 */ // public function kaiStats() // { // // 判断是否已有监听进程在运行 // $check = shell_exec("ps aux | grep 'queue:listen' | grep -v grep"); // if ($check) { // return json([ // 'code' => 1, // 'msg' => '监听进程已存在,请勿重复启动' // ]); // } // // 启动监听 // $command = 'nohup php think queue:listen --queue --timeout=300 --sleep=3 --memory=256 > /var/log/job_queue.log 2>&1 &'; // exec($command, $output, $status); // if ($status === 0) { // return json([ // 'code' => 0, // 'msg' => '队列监听已启动' // ]); // } else { // return json([ // 'code' => 1, // 'msg' => '队列启动失败', // 'output' => $output // ]); // } // } /** * 通过店铺ID-查询对应店铺表数据 * */ public function PatternApi() { $params = $this->request->param('pattern_id', ''); $tableName = 'pattern-' . $params; // 连接 MongoDB $mongo = Db::connect('mongodb'); // 查询指定 skc 的数据 $data = $mongo->table($tableName) ->field(' name, skc, file ') ->where("skc", '0853004152036') ->select(); $data = json_decode(json_encode($data), true); // 数组 return json([ 'code' => 0, 'msg' => '获取成功', 'data' => $data ]); } }