网站开发团队分工wordpress调用小工具栏
网站开发团队分工,wordpress调用小工具栏,天津网站设计开发,阿里云服务器上的网站怎么做修改Node.js调用Qwen-Image-Edit-F2P模型的RESTful API开发
你是不是也遇到过这样的场景#xff1a;手里有个很酷的AI图像编辑模型#xff0c;比如Qwen-Image-Edit-F2P#xff0c;但只能在本地用Python脚本跑#xff0c;想分享给同事用或者集成到自己的Web应用里#xff0c;就…Node.js调用Qwen-Image-Edit-F2P模型的RESTful API开发你是不是也遇到过这样的场景手里有个很酷的AI图像编辑模型比如Qwen-Image-Edit-F2P但只能在本地用Python脚本跑想分享给同事用或者集成到自己的Web应用里就特别麻烦。今天我就来手把手教你怎么用Node.js把这个模型包装成一个RESTful API服务。这样一来你就能像调用普通Web接口一样通过HTTP请求来生成图片了。不管是给前端页面用还是给移动端App调用都变得特别简单。1. 环境准备从零搭建Node.js开发环境在开始写代码之前我们得先把环境准备好。别担心跟着步骤走一点都不复杂。1.1 安装Node.js和npm如果你还没装Node.js先去官网下载安装包。建议选择LTS版本比较稳定。安装完成后打开命令行工具Windows用PowerShell或CMDMac用Terminal输入以下命令检查是否安装成功node --version npm --version如果能看到版本号比如v20.15.0和10.7.0就说明安装成功了。1.2 创建项目目录找个你喜欢的地方创建一个新文件夹作为项目目录mkdir qwen-image-api cd qwen-image-api1.3 初始化Node.js项目在这个目录下运行初始化命令npm init -y这个命令会创建一个package.json文件里面记录了项目的基本信息和依赖包。1.4 安装必要的依赖包我们需要几个关键的包来构建API服务npm install express multer axios sharp npm install --save-dev nodemon dotenv简单说一下这些包是干什么的express最流行的Node.js Web框架用来搭建API服务器multer处理文件上传我们得接收用户上传的图片axios发送HTTP请求用来调用Python服务sharp处理图片比如调整大小、转换格式nodemon开发工具代码改动后自动重启服务dotenv管理环境变量比如API密钥、端口号2. 基础概念理解我们的技术方案在开始写代码之前我先简单解释一下我们的整体思路这样你就能明白每一步在做什么。2.1 为什么需要Node.js中间层Qwen-Image-Edit-F2P模型本身是用Python写的依赖PyTorch等深度学习框架。直接在Node.js里运行Python模型很麻烦所以我们采用一个折中方案Python服务专门负责运行AI模型处理图片生成Node.js服务作为中间层接收HTTP请求调用Python服务返回结果这样分工明确Node.js负责Web接口和业务逻辑Python专心做AI计算。2.2 整体架构流程整个流程大概是这样的用户上传图片 → Node.js接收 → 调用Python服务 → 处理返回结果 → 返回给用户听起来是不是挺简单的接下来我们就一步步实现它。3. 分步实践构建完整的API服务现在开始写代码我会把关键部分都解释清楚确保你能看懂每一行在做什么。3.1 创建基础Express服务器首先在项目根目录创建一个server.js文件// server.js const express require(express); const multer require(multer); const path require(path); const fs require(fs); const axios require(axios); const sharp require(sharp); // 创建Express应用 const app express(); const PORT process.env.PORT || 3000; // 配置multer处理文件上传 const storage multer.diskStorage({ destination: function (req, file, cb) { const uploadDir uploads/; if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } cb(null, uploadDir); }, filename: function (req, file, cb) { // 生成唯一文件名避免重复 const uniqueSuffix Date.now() - Math.round(Math.random() * 1E9); cb(null, file.fieldname - uniqueSuffix path.extname(file.originalname)); } }); const upload multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024 // 限制10MB }, fileFilter: function (req, file, cb) { // 只允许图片文件 const allowedTypes /jpeg|jpg|png|webp/; const extname allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype allowedTypes.test(file.mimetype); if (mimetype extname) { return cb(null, true); } else { cb(new Error(只支持jpeg、jpg、png、webp格式的图片)); } } }); // 中间件配置 app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 静态文件服务用于访问生成的图片 app.use(/generated, express.static(generated)); // 健康检查接口 app.get(/health, (req, res) { res.json({ status: ok, timestamp: new Date().toISOString(), service: Qwen-Image-Edit-F2P API }); }); // 启动服务器 app.listen(PORT, () { console.log( 服务器已启动访问地址http://localhost:${PORT}); console.log( 上传文件目录uploads/); console.log( 生成文件目录generated/); });这个基础服务器已经可以运行了。在命令行输入node server.js如果看到服务器已启动的提示说明服务器启动成功了。3.2 实现核心的图片生成接口现在我们来添加最重要的功能接收用户上传的图片调用Python服务生成新图片。在server.js中添加以下代码// 创建生成图片的目录 const generatedDir generated/; if (!fs.existsSync(generatedDir)) { fs.mkdirSync(generatedDir, { recursive: true }); } // 核心的图片生成接口 app.post(/api/generate, upload.single(image), async (req, res) { try { console.log( 收到图片生成请求); // 检查是否上传了图片 if (!req.file) { return res.status(400).json({ error: 请上传图片文件, field: image }); } // 获取请求参数 const { prompt, negative_prompt , steps 40, guidance_scale 1.0 } req.body; if (!prompt) { return res.status(400).json({ error: 请输入提示词prompt, field: prompt }); } console.log( 提示词${prompt}); console.log( 上传文件${req.file.path}); // 这里我们先模拟处理下一节会替换为真实的Python服务调用 // 模拟处理延迟 await new Promise(resolve setTimeout(resolve, 2000)); // 生成结果文件名 const resultFilename result-${Date.now()}.png; const resultPath path.join(generatedDir, resultFilename); // 这里暂时用sharp处理一下原图作为示例 // 实际开发中这里会调用Python服务 await sharp(req.file.path) .resize(1024, 1024, { fit: inside }) .toFile(resultPath); // 返回结果 res.json({ success: true, message: 图片生成成功, data: { original_image: /uploads/${path.basename(req.file.path)}, generated_image: /generated/${resultFilename}, prompt: prompt, timestamp: new Date().toISOString() } }); console.log( 图片生成完成${resultFilename}); } catch (error) { console.error( 处理请求时出错, error); res.status(500).json({ error: 服务器内部错误, message: error.message }); } });这个接口已经可以接收图片和提示词了不过现在还是模拟处理。别急我们马上就来连接真正的Python服务。3.3 配置Python服务连接我们需要一个Python服务来处理AI模型。假设你已经有一个运行在http://localhost:8000的Python服务它提供了图片生成的API。在server.js中添加一个函数来调用Python服务// Python服务的配置 const PYTHON_SERVICE_URL process.env.PYTHON_SERVICE_URL || http://localhost:8000; // 调用Python服务的函数 async function callPythonService(imagePath, prompt, negative_prompt, steps, guidance_scale) { try { console.log( 调用Python服务${PYTHON_SERVICE_URL}/generate); // 读取图片文件 const imageBuffer fs.readFileSync(imagePath); // 将图片转换为base64方便传输 const base64Image imageBuffer.toString(base64); // 准备请求数据 const requestData { image: base64Image, prompt: prompt, negative_prompt: negative_prompt, steps: parseInt(steps), guidance_scale: parseFloat(guidance_scale), timestamp: new Date().toISOString() }; // 发送请求到Python服务 const response await axios.post(${PYTHON_SERVICE_URL}/generate, requestData, { headers: { Content-Type: application/json, Accept: application/json }, timeout: 300000 // 5分钟超时生成图片可能需要较长时间 }); console.log( Python服务响应状态${response.status}); return response.data; } catch (error) { console.error( 调用Python服务失败, error.message); if (error.response) { console.error(响应数据, error.response.data); console.error(响应状态, error.response.status); } throw new Error(Python服务调用失败${error.message}); } }现在我们来修改之前的/api/generate接口让它真正调用Python服务// 修改后的图片生成接口 app.post(/api/generate, upload.single(image), async (req, res) { try { console.log( 收到图片生成请求); // 检查是否上传了图片 if (!req.file) { return res.status(400).json({ error: 请上传图片文件, field: image }); } // 获取请求参数 const { prompt, negative_prompt , steps 40, guidance_scale 1.0, width 1024, height 1024 } req.body; if (!prompt) { return res.status(400).json({ error: 请输入提示词prompt, field: prompt }); } console.log( 提示词${prompt}); console.log( 上传文件${req.file.path}); // 调用Python服务 const pythonResult await callPythonService( req.file.path, prompt, negative_prompt, steps, guidance_scale ); // 处理Python服务返回的base64图片 if (!pythonResult.image) { throw new Error(Python服务未返回图片数据); } // 生成结果文件名 const resultFilename result-${Date.now()}.png; const resultPath path.join(generatedDir, resultFilename); // 将base64图片保存为文件 const imageBuffer Buffer.from(pythonResult.image, base64); fs.writeFileSync(resultPath, imageBuffer); console.log( 图片已保存${resultPath}); // 返回结果 res.json({ success: true, message: 图片生成成功, data: { original_image: /uploads/${path.basename(req.file.path)}, generated_image: /generated/${resultFilename}, prompt: prompt, negative_prompt: negative_prompt, steps: steps, guidance_scale: guidance_scale, processing_time: pythonResult.processing_time || 未知, timestamp: new Date().toISOString() } }); console.log( 图片生成完成${resultFilename}); } catch (error) { console.error( 处理请求时出错, error); // 清理上传的文件如果出错 if (req.file fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); } res.status(500).json({ error: 图片生成失败, message: error.message, suggestion: 请检查Python服务是否正常运行或联系管理员 }); } });3.4 添加批量处理和状态查询功能一个完整的API服务还需要一些辅助功能。我们来添加批量处理接口和任务状态查询// 存储处理任务的状态 const processingTasks new Map(); // 批量图片生成接口 app.post(/api/batch-generate, upload.array(images, 10), async (req, res) { try { if (!req.files || req.files.length 0) { return res.status(400).json({ error: 请上传至少一张图片, field: images }); } const { prompt, negative_prompt } req.body; if (!prompt) { return res.status(400).json({ error: 请输入提示词prompt, field: prompt }); } // 创建批量任务 const taskId batch-${Date.now()}-${Math.random().toString(36).substr(2, 9)}; const task { id: taskId, status: processing, total: req.files.length, completed: 0, failed: 0, results: [], start_time: new Date().toISOString(), end_time: null }; processingTasks.set(taskId, task); // 立即返回任务ID让客户端可以查询进度 res.json({ success: true, message: 批量任务已创建, data: { task_id: taskId, status_url: /api/tasks/${taskId} } }); // 异步处理所有图片 processBatchImages(taskId, req.files, prompt, negative_prompt); } catch (error) { console.error( 创建批量任务失败, error); res.status(500).json({ error: 创建批量任务失败, message: error.message }); } }); // 异步处理批量图片的函数 async function processBatchImages(taskId, files, prompt, negative_prompt) { const task processingTasks.get(taskId); for (let i 0; i files.length; i) { const file files[i]; try { console.log( 处理批量任务 ${taskId} 的第 ${i 1} 张图片); // 调用Python服务处理单张图片 const pythonResult await callPythonService( file.path, prompt, negative_prompt, 40, // 默认步数 1.0 // 默认引导系数 ); // 保存生成的图片 const resultFilename batch-${taskId}-${i 1}.png; const resultPath path.join(generatedDir, resultFilename); if (pythonResult.image) { const imageBuffer Buffer.from(pythonResult.image, base64); fs.writeFileSync(resultPath, imageBuffer); task.results.push({ index: i 1, original: /uploads/${path.basename(file.path)}, generated: /generated/${resultFilename}, success: true }); } else { task.results.push({ index: i 1, original: /uploads/${path.basename(file.path)}, error: 未生成图片, success: false }); task.failed; } } catch (error) { console.error( 处理第 ${i 1} 张图片失败, error.message); task.results.push({ index: i 1, original: /uploads/${path.basename(file.path)}, error: error.message, success: false }); task.failed; } task.completed; // 更新任务进度 processingTasks.set(taskId, task); // 稍微延迟一下避免请求过于密集 await new Promise(resolve setTimeout(resolve, 100)); } // 所有图片处理完成 task.status completed; task.end_time new Date().toISOString(); processingTasks.set(taskId, task); console.log( 批量任务 ${taskId} 完成${task.completed - task.failed} 成功${task.failed} 失败); } // 查询任务状态接口 app.get(/api/tasks/:taskId, (req, res) { const { taskId } req.params; const task processingTasks.get(taskId); if (!task) { return res.status(404).json({ error: 任务不存在, task_id: taskId }); } res.json({ success: true, data: { ...task, progress: task.total 0 ? Math.round((task.completed / task.total) * 100) : 0 } }); }); // 获取所有任务列表 app.get(/api/tasks, (req, res) { const tasks Array.from(processingTasks.entries()).map(([id, task]) ({ id, status: task.status, progress: task.total 0 ? Math.round((task.completed / task.total) * 100) : 0, total: task.total, completed: task.completed, failed: task.failed, start_time: task.start_time, end_time: task.end_time })); res.json({ success: true, data: { tasks: tasks, total: tasks.length, processing: tasks.filter(t t.status processing).length, completed: tasks.filter(t t.status completed).length } }); });3.5 添加图片预处理和格式转换接口有时候用户上传的图片可能需要预处理比如调整大小、裁剪人脸区域等。我们添加一些实用的预处理接口// 图片预处理接口调整大小 app.post(/api/preprocess/resize, upload.single(image), async (req, res) { try { if (!req.file) { return res.status(400).json({ error: 请上传图片 }); } const { width 1024, height 1024, fit inside } req.body; // 生成处理后的文件名 const processedFilename resized-${Date.now()}.png; const processedPath path.join(generatedDir, processedFilename); // 使用sharp调整图片大小 await sharp(req.file.path) .resize(parseInt(width), parseInt(height), { fit: fit, // cover, contain, fill, inside, outside withoutEnlargement: true }) .toFormat(png) .toFile(processedPath); res.json({ success: true, message: 图片调整大小成功, data: { original: /uploads/${path.basename(req.file.path)}, processed: /generated/${processedFilename}, width: width, height: height, fit: fit } }); } catch (error) { console.error( 图片预处理失败, error); res.status(500).json({ error: 图片预处理失败, message: error.message }); } }); // 图片格式转换接口 app.post(/api/convert, upload.single(image), async (req, res) { try { if (!req.file) { return res.status(400).json({ error: 请上传图片 }); } const { format png, quality 90 } req.body; const allowedFormats [jpeg, jpg, png, webp]; if (!allowedFormats.includes(format.toLowerCase())) { return res.status(400).json({ error: 不支持的格式, supported: allowedFormats }); } // 生成转换后的文件名 const convertedFilename converted-${Date.now()}.${format}; const convertedPath path.join(generatedDir, convertedFilename); // 使用sharp转换格式 const sharpInstance sharp(req.file.path); if (format jpeg || format jpg) { sharpInstance.jpeg({ quality: parseInt(quality) }); } else if (format webp) { sharpInstance.webp({ quality: parseInt(quality) }); } else if (format png) { sharpInstance.png({ quality: parseInt(quality) }); } await sharpInstance.toFile(convertedPath); res.json({ success: true, message: 图片格式转换成功, data: { original: /uploads/${path.basename(req.file.path)}, converted: /generated/${convertedFilename}, format: format, quality: quality } }); } catch (error) { console.error( 图片格式转换失败, error); res.status(500).json({ error: 图片格式转换失败, message: error.message }); } });4. 快速上手示例测试我们的API服务代码写完了我们来实际测试一下。首先我们需要创建一个简单的Python服务来模拟Qwen-Image-Edit-F2P模型。4.1 创建模拟Python服务在项目根目录创建一个python_server.py文件# python_server.py from flask import Flask, request, jsonify import base64 from io import BytesIO from PIL import Image import numpy as np import time import os app Flask(__name__) app.route(/health, methods[GET]) def health(): return jsonify({ status: ok, service: Qwen-Image-Edit-F2P Simulator, timestamp: time.strftime(%Y-%m-%d %H:%M:%S) }) app.route(/generate, methods[POST]) def generate(): try: data request.json # 获取base64图片 image_data data.get(image, ) prompt data.get(prompt, ) negative_prompt data.get(negative_prompt, ) steps data.get(steps, 40) print(f收到生成请求prompt{prompt}, steps{steps}) # 解码base64图片 image_bytes base64.b64decode(image_data) image Image.open(BytesIO(image_bytes)) # 这里模拟AI处理过程 # 实际使用时这里应该调用真正的Qwen-Image-Edit-F2P模型 print(f图片尺寸{image.size}) # 模拟处理时间根据步数调整 processing_time steps * 0.1 # 每步0.1秒 time.sleep(min(processing_time, 2)) # 最多等待2秒 # 这里简单处理将图片转换为numpy数组并添加一些随机变化 img_array np.array(image) # 模拟AI生成效果轻微调整颜色 # 实际应该用真正的模型生成新图片 if len(img_array.shape) 3: # 随机调整颜色通道模拟风格变化 for i in range(3): adjustment np.random.randint(-20, 20) img_array[:, :, i] np.clip(img_array[:, :, i].astype(np.int32) adjustment, 0, 255) # 创建新图片 result_image Image.fromarray(img_array.astype(np.uint8)) # 将结果转换为base64 buffered BytesIO() result_image.save(buffered, formatPNG) result_base64 base64.b64encode(buffered.getvalue()).decode(utf-8) return jsonify({ success: True, image: result_base64, processing_time: f{processing_time:.2f}秒, model: Qwen-Image-Edit-F2P, steps: steps, prompt: prompt }) except Exception as e: return jsonify({ success: False, error: str(e) }), 500 if __name__ __main__: port int(os.environ.get(PORT, 8000)) print(f Python模拟服务启动端口{port}) print(f 健康检查http://localhost:{port}/health) app.run(host0.0.0.0, portport, debugFalse)运行这个Python服务python python_server.py4.2 启动Node.js服务打开另一个命令行窗口启动我们的Node.js服务node server.js4.3 使用curl测试API现在我们可以用curl命令来测试API了。首先测试健康检查curl http://localhost:3000/health应该能看到类似这样的响应{ status: ok, timestamp: 2024-01-15T10:30:00.000Z, service: Qwen-Image-Edit-F2P API }4.4 使用Postman或浏览器测试图片生成如果你有Postman可以这样测试创建一个POST请求http://localhost:3000/api/generate选择form-data格式添加字段image选择一张图片文件prompt输入描述比如将这个人像转换为卡通风格negative_prompt可选输入负面提示词steps可选生成步数默认40发送请求后你会收到一个JSON响应里面包含生成图片的URL。5. 实用技巧与进阶配置基本的API服务已经能用了但要让它在生产环境稳定运行还需要一些优化和配置。5.1 添加环境变量配置创建.env文件来管理配置# .env PORT3000 PYTHON_SERVICE_URLhttp://localhost:8000 NODE_ENVdevelopment UPLOAD_LIMIT10MB MAX_FILES10 API_TIMEOUT300000修改server.js在文件开头加载环境变量// 加载环境变量 if (process.env.NODE_ENV ! production) { require(dotenv).config(); } // 使用环境变量 const PORT process.env.PORT || 3000; const PYTHON_SERVICE_URL process.env.PYTHON_SERVICE_URL || http://localhost:8000; const API_TIMEOUT parseInt(process.env.API_TIMEOUT) || 300000;5.2 添加请求日志和监控添加一个简单的日志中间件// 日志中间件 app.use((req, res, next) { const start Date.now(); const originalSend res.send; res.send function(body) { const duration Date.now() - start; console.log(${new Date().toISOString()} ${req.method} ${req.url} ${res.statusCode} ${duration}ms); return originalSend.call(this, body); }; next(); }); // 错误处理中间件 app.use((err, req, res, next) { console.error( 未处理的错误, err); if (err instanceof multer.MulterError) { if (err.code LIMIT_FILE_SIZE) { return res.status(400).json({ error: 文件太大, message: 文件大小不能超过${process.env.UPLOAD_LIMIT || 10MB} }); } } res.status(500).json({ error: 服务器内部错误, message: process.env.NODE_ENV development ? err.message : 请稍后重试 }); });5.3 添加API文档端点为了方便其他开发者使用我们可以添加一个简单的API文档// API文档接口 app.get(/api/docs, (req, res) { res.json({ service: Qwen-Image-Edit-F2P RESTful API, version: 1.0.0, endpoints: [ { method: GET, path: /health, description: 健康检查, authentication: 不需要 }, { method: POST, path: /api/generate, description: 单张图片生成, authentication: 不需要, parameters: [ { name: image, type: file, required: true, description: 上传的图片文件 }, { name: prompt, type: string, required: true, description: 生成提示词 }, { name: negative_prompt, type: string, required: false, description: 负面提示词 }, { name: steps, type: number, required: false, default: 40, description: 生成步数 } ] }, { method: POST, path: /api/batch-generate, description: 批量图片生成, authentication: 不需要, parameters: [ { name: images, type: file[], required: true, description: 上传的多张图片文件最多10张 }, { name: prompt, type: string, required: true, description: 生成提示词 } ] }, { method: GET, path: /api/tasks/:taskId, description: 查询任务状态, authentication: 不需要 } ], examples: { curl: curl -X POST -F imagetest.jpg -F prompt将图片转换为卡通风格 http://localhost:3000/api/generate, python: import requests\nresponse requests.post(http://localhost:3000/api/generate, files{image: open(test.jpg, rb)}, data{prompt: 卡通风格}) } }); });5.4 添加简单的身份验证对于生产环境我们可能需要添加基本的身份验证// 简单的API密钥验证 const API_KEYS new Set(process.env.API_KEYS ? process.env.API_KEYS.split(,) : []); // 身份验证中间件 const authenticate (req, res, next) { // 在开发环境跳过验证 if (process.env.NODE_ENV development) { return next(); } const apiKey req.headers[x-api-key] || req.query.api_key; if (!apiKey) { return res.status(401).json({ error: 未提供API密钥, message: 请在请求头中添加 x-api-key 或在查询参数中添加 api_key }); } if (!API_KEYS.has(apiKey)) { return res.status(403).json({ error: 无效的API密钥, message: 请检查API密钥是否正确 }); } next(); }; // 对需要保护的接口应用身份验证 app.post(/api/generate, authenticate, upload.single(image), async (req, res) { // ... 原有的处理逻辑 }); app.post(/api/batch-generate, authenticate, upload.array(images, 10), async (req, res) { // ... 原有的处理逻辑 });6. 常见问题解答在实际使用中你可能会遇到一些问题。这里我整理了一些常见问题和解决方法。Q: 上传图片时提示文件太大怎么办A: 检查.env文件中的UPLOAD_LIMIT设置或者修改multer配置中的limits.fileSize。也可以先压缩图片再上传。Q: 调用Python服务超时怎么办A: 图片生成可能需要较长时间可以调整API_TIMEOUT环境变量或者在调用axios时增加timeout设置。Q: 生成的图片质量不高怎么办A: 可以尝试调整生成参数增加steps参数如50-100步优化prompt描述更详细具体使用合适的negative_prompt排除不想要的效果Q: 如何提高并发处理能力A: 可以考虑以下方案使用Node.js集群模式启动多个进程部署多个Python服务实例使用负载均衡使用消息队列如RabbitMQ处理生成任务Q: 如何监控服务运行状态A: 除了我们添加的日志还可以使用PM2等进程管理器集成监控工具如Prometheus添加更详细的服务指标收集7. 总结走完这一整套流程你应该已经掌握了如何用Node.js为Qwen-Image-Edit-F2P模型构建RESTful API服务。从环境搭建到接口实现再到优化配置我们一步步构建了一个相对完整的解决方案。实际用下来这种架构的灵活性确实不错。Node.js负责处理Web请求和业务逻辑Python专心做AI计算各司其职。你还可以根据自己的需求继续扩展比如添加更多预处理功能、支持更多模型参数、集成对象存储服务等。如果你在部署过程中遇到什么问题或者有新的想法想要实现欢迎随时交流。技术就是这样一边用一边改进慢慢就会越来越完善。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。