新功能: - 创建AI聊天助手组件(AIChatWidget) - 支持文字+图片多模态对话 - 集成Gemini AI API - 支持图片编辑和内容生成 - 实时对话历史管理 - 优雅的UI设计,支持黑夜模式 技术实现: - 后端API代理Gemini API(/api/ai-chat) - 支持最多5张图片上传 - 流式响应处理 - 对话历史管理 - Base64图片处理 配置: - 在.env中添加Gemini API配置 - GEMINI_API_KEY - GEMINI_API_BASE_URL - GEMINI_MODEL 替换原弹幕功能,提供更强大的AI助手体验!
3412 lines
130 KiB
Plaintext
Executable File
3412 lines
130 KiB
Plaintext
Executable File
const express = require('express');
|
||
const multer = require('multer');
|
||
const cors = require('cors');
|
||
const dotenv = require('dotenv');
|
||
const Minio = require('minio');
|
||
const fetch = require('node-fetch');
|
||
const path = require('path');
|
||
const net = require('net');
|
||
const FormData = require('form-data');
|
||
const crypto = require('crypto');
|
||
const cookieParser = require('cookie-parser');
|
||
const { connectDB } = require('./db/mongoose');
|
||
const { optionalAuth } = require('./middleware/auth');
|
||
const mongoose = require('mongoose');
|
||
const uuid = require('uuid');
|
||
const fs = require('fs');
|
||
const os = require('os');
|
||
const { saveGeneratedImage, getGeneratedImagesByUserAndType, getAllGeneratedImagesByUser } = require('./utils/image-utils');
|
||
const jwt = require('jsonwebtoken');
|
||
|
||
// 导入路由
|
||
const authRoutes = require('./routes/auth');
|
||
const userRoutes = require('./routes/user');
|
||
const adminRoutes = require('./routes/admin');
|
||
const evaluationRoutes = require('./routes/evaluation');
|
||
const removeBgRoutes = require('./routes/remove-bg');
|
||
const imageEnhancerRoutes = require('./routes/image-enhancer'); // 添加图片变清晰路由
|
||
const imageMattingRoutes = require('./routes/image-matting'); // 添加智能抠图路由
|
||
const productReplacementRoutes = require('./routes/product-replacement'); // 添加商品替换路由
|
||
const aiAssistantRoutes = require('./routes/ai-assistant'); // 添加AI助手路由
|
||
const notificationsRoutes = require('./routes/notifications'); // 导入通知路由
|
||
const settingsRoutes = require('./routes/api/settings'); // 导入设置路由
|
||
const { sketchToImageRouter, tripo3dRouter, router: indexRouter } = require('./routes/index'); // 导入侧边栏功能设置路由
|
||
const ocrRoutes = require('./routes/ocr'); // 导入OCR路由
|
||
const eraseRoutes = require('./routes/erase'); // 导入涂抹去除路由
|
||
const sceneFissionRoutes = require('./routes/scene-fission'); // 导入场景裂变路由
|
||
const imageGenerator2Routes = require('./routes/image-generator-2'); // 导入图片生成2.0路由
|
||
|
||
// 导入清理模块
|
||
const cleanup = require('./utils/cleanup');
|
||
|
||
dotenv.config();
|
||
|
||
const app = express();
|
||
|
||
// 添加文件上传大小限制 - 修改为更大的值
|
||
app.use(express.json({ limit: '100mb' }));
|
||
app.use(express.urlencoded({ limit: '100mb', extended: true }));
|
||
app.use(cookieParser());
|
||
|
||
// 允许所有来源的请求
|
||
app.use(cors({
|
||
origin: function(origin, callback) {
|
||
// 允许没有来源的请求(如移动应用或curl)
|
||
if (!origin) return callback(null, true);
|
||
|
||
// 允许的来源列表
|
||
return callback(null, true);
|
||
},
|
||
credentials: true
|
||
}));
|
||
|
||
// 添加健康检查路由
|
||
app.get('/api/health', (req, res) => {
|
||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||
});
|
||
|
||
// 配置静态文件服务
|
||
app.use(express.static(path.join(__dirname, '../public')));
|
||
app.use('/uploads', express.static(path.join(__dirname, '../public/uploads')));
|
||
app.use('/uploads/processed', express.static(path.join(__dirname, 'uploads/processed')));
|
||
|
||
// 添加路径调试信息和额外的静态文件路径
|
||
console.log('静态文件路径配置:');
|
||
console.log('- 公共路径:', path.join(__dirname, '../public'));
|
||
console.log('- 上传路径1:', path.join(__dirname, '../public/uploads'));
|
||
console.log('- 上传路径2:', path.join(__dirname, 'uploads/processed'));
|
||
|
||
// 确保访问 /server/uploads/processed 也能工作
|
||
app.use('/server/uploads/processed', express.static(path.join(__dirname, 'uploads/processed')));
|
||
|
||
// 可选的认证中间件 - 不会阻止未认证的请求
|
||
app.use(optionalAuth);
|
||
|
||
// MinIO客户端配置
|
||
let minioClient;
|
||
if (process.env.MINIO_ENDPOINT && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY) {
|
||
minioClient = new Minio.Client({
|
||
endPoint: process.env.MINIO_ENDPOINT,
|
||
port: parseInt(process.env.MINIO_PORT || '9000'),
|
||
useSSL: process.env.MINIO_USE_SSL === 'true',
|
||
accessKey: process.env.MINIO_ACCESS_KEY,
|
||
secretKey: process.env.MINIO_SECRET_KEY
|
||
});
|
||
console.log('MinIO客户端已配置');
|
||
} else {
|
||
console.warn('⚠️ MinIO配置不完整,文件上传功能可能不可用');
|
||
// 创建一个空的MinIO客户端,避免代码中的null引用
|
||
minioClient = {
|
||
bucketExists: async () => {
|
||
console.warn('MinIO客户端未配置,bucketExists操作被跳过');
|
||
return false;
|
||
},
|
||
makeBucket: async () => {
|
||
console.warn('MinIO客户端未配置,makeBucket操作被跳过');
|
||
return false;
|
||
},
|
||
putObject: async () => {
|
||
console.warn('MinIO客户端未配置,putObject操作被跳过');
|
||
throw new Error('MinIO客户端未配置');
|
||
},
|
||
listObjects: () => {
|
||
console.warn('MinIO客户端未配置,listObjects操作被跳过');
|
||
const emitter = new require('events').EventEmitter();
|
||
setTimeout(() => {
|
||
emitter.emit('end');
|
||
}, 10);
|
||
return emitter;
|
||
}
|
||
};
|
||
}
|
||
|
||
// 配置multer存储
|
||
const storage = multer.diskStorage({
|
||
destination: function(req, file, cb) {
|
||
const uploadDir = path.join(__dirname, '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
|
||
});
|
||
|
||
// 创建两个不同的 multer 实例,一个用于单文件上传,一个用于多文件上传
|
||
const singleUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: {
|
||
fileSize: 20 * 1024 * 1024 } // 20MB
|
||
}).single('image');
|
||
|
||
// 添加file字段的单文件上传multer实例,专门用于/api/upload路由
|
||
const fileUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: {
|
||
fileSize: 20 * 1024 * 1024 } // 20MB
|
||
}).single('file');
|
||
|
||
const multiUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: {
|
||
fileSize: 20 * 1024 * 1024 // 20MB
|
||
}
|
||
}).fields([
|
||
{ name: 'base_image', maxCount: 1 },
|
||
{ name: 'ref_image', maxCount: 1 }
|
||
]);
|
||
|
||
// 添加测试路由 - 确保在其他路由注册之前添加
|
||
app.get('/api/test', (req, res) => {
|
||
console.log('测试路由被调用');
|
||
res.json({ message: '服务器正常运行' });
|
||
});
|
||
|
||
// 添加MinIO测试路由
|
||
app.get('/api/test-minio', async (req, res) => {
|
||
try {
|
||
console.log('测试MinIO连接...');
|
||
|
||
// 检查MinIO配置
|
||
if (!process.env.MINIO_BUCKET_NAME || !process.env.MINIO_ENDPOINT || !process.env.MINIO_PORT) {
|
||
throw new Error('MinIO配置不完整');
|
||
}
|
||
|
||
// 检查bucket是否存在
|
||
const bucketExists = await minioClient.bucketExists(process.env.MINIO_BUCKET_NAME);
|
||
if (!bucketExists) {
|
||
console.log(`Bucket ${process.env.MINIO_BUCKET_NAME} 不存在,正在创建...`);
|
||
await minioClient.makeBucket(process.env.MINIO_BUCKET_NAME);
|
||
console.log(`Bucket ${process.env.MINIO_BUCKET_NAME} 创建成功`);
|
||
} else {
|
||
console.log(`Bucket ${process.env.MINIO_BUCKET_NAME} 已存在`);
|
||
}
|
||
|
||
// 列出bucket中的对象
|
||
const objects = await minioClient.listObjects(process.env.MINIO_BUCKET_NAME, '', true);
|
||
const objectList = [];
|
||
|
||
objects.on('data', (obj) => {
|
||
objectList.push(obj.name);
|
||
});
|
||
|
||
objects.on('end', () => {
|
||
res.json({
|
||
success: true,
|
||
message: 'MinIO连接正常',
|
||
config: {
|
||
endpoint: process.env.MINIO_ENDPOINT,
|
||
port: process.env.MINIO_PORT,
|
||
bucket: process.env.MINIO_BUCKET_NAME,
|
||
useSSL: process.env.MINIO_USE_SSL === 'true'
|
||
},
|
||
objects: objectList.slice(0, 10) // 只返回前10个对象
|
||
});
|
||
});
|
||
|
||
objects.on('error', (err) => {
|
||
throw err;
|
||
});
|
||
} catch (error) {
|
||
console.error('MinIO测试错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: `MinIO连接测试失败: ${error.message}`,
|
||
config: {
|
||
endpoint: process.env.MINIO_ENDPOINT,
|
||
port: process.env.MINIO_PORT,
|
||
bucket: process.env.MINIO_BUCKET_NAME,
|
||
useSSL: process.env.MINIO_USE_SSL === 'true'
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// 添加简单的文件上传测试路由
|
||
app.post('/api/test-upload', (req, res) => {
|
||
console.log('测试上传路由被调用');
|
||
|
||
// 创建一个简单的multer实例
|
||
const testUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: { fileSize: 5 * 1024 * 1024 } // 5MB
|
||
}).single('testImage');
|
||
|
||
testUpload(req, res, async function(err) {
|
||
if (err) {
|
||
console.error('测试上传错误:', err);
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '文件上传错误: ' + err.message
|
||
});
|
||
}
|
||
|
||
// 检查文件是否存在
|
||
if (!req.file) {
|
||
console.error('未找到上传的文件');
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '未上传文件'
|
||
});
|
||
}
|
||
|
||
// 记录文件信息
|
||
console.log('测试上传成功:', {
|
||
originalname: req.file.originalname,
|
||
mimetype: req.file.mimetype,
|
||
size: req.file.size,
|
||
fieldname: req.file.fieldname
|
||
});
|
||
|
||
try {
|
||
// 尝试上传到MinIO
|
||
const fileUrl = await uploadToMinio(req.file);
|
||
console.log('MinIO上传成功,URL:', fileUrl);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '文件上传成功',
|
||
file: {
|
||
name: req.file.originalname,
|
||
type: req.file.mimetype,
|
||
size: req.file.size
|
||
},
|
||
url: fileUrl
|
||
});
|
||
} catch (error) {
|
||
console.error('MinIO上传错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '文件上传到MinIO失败: ' + error.message
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// 添加测试登录路由
|
||
app.post('/api/test-login', (req, res) => {
|
||
console.log('收到测试登录请求:', req.body);
|
||
res.json({
|
||
success: true,
|
||
message: '测试登录成功',
|
||
user: {
|
||
id: '123456',
|
||
username: 'testuser',
|
||
email: req.body.email || 'test@example.com',
|
||
role: 'admin'
|
||
}
|
||
});
|
||
});
|
||
|
||
// 配置路由
|
||
app.use('/api/auth', authRoutes);
|
||
app.use('/api/users', userRoutes);
|
||
app.use('/api/admin', adminRoutes);
|
||
app.use('/api/evaluation', evaluationRoutes);
|
||
app.use('/api/settings', settingsRoutes);
|
||
app.use('/api', indexRouter);
|
||
app.use('/api/ocr', ocrRoutes);
|
||
app.use('/api/erase', eraseRoutes);
|
||
app.use('/api/remove-bg', removeBgRoutes);
|
||
app.use('/api', imageEnhancerRoutes); // 修改为直接使用/api前缀,这样/enhance-image路由就会变成/api/enhance-image
|
||
app.use('/api', imageMattingRoutes); // 注册智能抠图路由
|
||
app.use('/api', productReplacementRoutes); // 注册商品替换路由
|
||
app.use('/api/scene-fission', sceneFissionRoutes); // 注册场景裂变路由
|
||
app.use('/api/ai-assistant', aiAssistantRoutes); // 注册AI助手路由
|
||
app.use('/api/chat', aiAssistantRoutes); // 添加直接访问/api/chat的路由映射
|
||
app.use('/api/notifications', notificationsRoutes); // 注册通知路由
|
||
app.use('/api/image-generator-2', imageGenerator2Routes); // 注册图片生成2.0路由
|
||
|
||
// 注册涂抹去除路由并打印日志
|
||
console.log('正在注册涂抹去除路由...');
|
||
app.use('/api/erase', eraseRoutes);
|
||
console.log('✅ 涂抹去除路由注册成功');
|
||
|
||
// 打印所有已注册的涂抹去除路由
|
||
console.log('=== 涂抹去除路由列表 ===');
|
||
console.log('POST /api/erase/upload - 上传图片和掩码');
|
||
console.log('POST /api/erase/process - 处理图片擦除');
|
||
console.log('GET /api/erase/check-task/:taskId - 检查任务状态');
|
||
console.log('GET /api/erase/test - 测试路由');
|
||
console.log('=== 涂抹去除路由列表结束 ===');
|
||
|
||
// 在其他中间件之后,路由之前连接数据库
|
||
console.log('尝试连接数据库...');
|
||
connectDB()
|
||
.then(connection => {
|
||
if (connection.readyState === 1) {
|
||
console.log('数据库连接成功');
|
||
} else {
|
||
console.warn('数据库连接未就绪,但应用将继续启动。部分功能可能不可用。');
|
||
}
|
||
})
|
||
.catch(err => {
|
||
console.error('数据库连接失败:', err.message);
|
||
console.warn('应用将在没有数据库连接的情况下启动。用户认证和其他需要数据库的功能将不可用。');
|
||
});
|
||
|
||
// 路由处理器函数
|
||
const routeHandlers = {
|
||
// 去除背景处理器
|
||
async removeBgHandler(req, res) {
|
||
try {
|
||
console.log('调用抠图API...');
|
||
|
||
// 检查请求文件
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '未上传图片文件'
|
||
});
|
||
}
|
||
|
||
// 获取请求参数
|
||
const type = req.body.type || 'general';
|
||
const format = req.body.format || 'png';
|
||
const apiKey = req.body.api_key || process.env.REMOVE_BG_API_KEY_V1 || 'wxfxd0rmel2r1l2iv';
|
||
|
||
// 创建抠图任务 - 使用同步API
|
||
const formData = new FormData();
|
||
|
||
// 添加文件
|
||
formData.append('image_file', req.file.buffer, {
|
||
filename: req.file.originalname,
|
||
contentType: req.file.mimetype
|
||
});
|
||
|
||
// 根据类型映射
|
||
if (type === 'person') {
|
||
formData.append('type', 'person');
|
||
} else if (type === 'product' || type === 'general') {
|
||
formData.append('type', 'object');
|
||
} else if (type === 'car') {
|
||
formData.append('type', 'object');
|
||
} else if (type === 'animal') {
|
||
formData.append('type', 'object');
|
||
}
|
||
|
||
// 设置输出格式
|
||
formData.append('format', format);
|
||
if (format === 'jpg') {
|
||
formData.append('bg_color', 'ffffff');
|
||
}
|
||
|
||
// 设置同步模式
|
||
formData.append('sync', '1');
|
||
|
||
// 设置返回类型
|
||
formData.append('return_type', '1');
|
||
formData.append('output_type', '2');
|
||
|
||
console.log('发送请求到抠图API...');
|
||
|
||
const apiUrl = process.env.REMOVE_BG_API_URL_V1 || 'https://techsz.aoscdn.com/api/tasks/visual/segmentation';
|
||
const response = await fetch(apiUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-API-KEY': apiKey
|
||
},
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
console.error('抠图API错误响应:', errorData);
|
||
throw new Error(`抠图任务失败: ${JSON.stringify(errorData)}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('抠图API响应:', result);
|
||
|
||
// 获取用户ID(如果已登录)
|
||
let userId = null;
|
||
if (req.session && req.session.user && req.session.user._id) {
|
||
userId = req.session.user._id;
|
||
} else if (req.cookies && req.cookies.token) {
|
||
try {
|
||
const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
|
||
userId = decoded._id;
|
||
} catch (err) {
|
||
console.error('解析token失败:', err);
|
||
}
|
||
}
|
||
|
||
// 保存图片信息到数据库
|
||
if (userId) {
|
||
try {
|
||
await saveGeneratedImage({
|
||
imageUrl: result.data.image,
|
||
type: 'remove-bg',
|
||
originalImageUrl: null, // 这里可以保存原始图片URL,如果有的话
|
||
userId: userId,
|
||
taskId: result.data.task_id,
|
||
metadata: {
|
||
format: format,
|
||
type: type
|
||
}
|
||
});
|
||
console.log('已保存抠图结果到用户图片库');
|
||
} catch (saveError) {
|
||
console.error('保存图片信息失败:', saveError);
|
||
// 继续处理,不影响主流程
|
||
}
|
||
} else {
|
||
console.log('用户未登录,不保存图片信息');
|
||
}
|
||
|
||
// 返回处理结果
|
||
res.json({
|
||
success: true,
|
||
message: '背景去除成功',
|
||
imageUrl: result.data.image, // 兼容旧版前端
|
||
data: {
|
||
url: result.data.image // 兼容新版前端
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('抠图API错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// 去除背景2.0处理器 - 使用Remove.bg API
|
||
async removeBgV2Handler(req, res) {
|
||
try {
|
||
console.log('调用Remove.bg API去除背景...');
|
||
|
||
// 检查请求文件
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '未上传图片文件'
|
||
});
|
||
}
|
||
|
||
// 获取API密钥
|
||
const apiKey = process.env.REMOVE_BG_API_KEY || 'c5qMk4gFGN6dDrpV9HFvKfMN';
|
||
|
||
// 创建FormData对象
|
||
const formData = new FormData();
|
||
|
||
// 添加文件
|
||
formData.append('image_file', req.file.buffer, {
|
||
filename: req.file.originalname,
|
||
contentType: req.file.mimetype
|
||
});
|
||
|
||
// 设置输出格式和大小
|
||
formData.append('size', 'auto');
|
||
formData.append('format', 'png');
|
||
|
||
console.log('发送请求到Remove.bg API...');
|
||
|
||
// 获取API URL
|
||
const apiUrl = process.env.REMOVE_BG_API_URL || 'https://api.remove.bg/v1.0/removebg';
|
||
|
||
// 发送请求
|
||
const response = await fetch(apiUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-Api-Key': apiKey
|
||
},
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
let errorMessage = `Remove.bg API错误: ${response.status} ${response.statusText}`;
|
||
|
||
try {
|
||
const errorData = await response.json();
|
||
console.error('Remove.bg API错误响应:', errorData);
|
||
errorMessage = errorData.errors ? errorData.errors[0].title : errorMessage;
|
||
} catch (e) {
|
||
console.error('无法解析错误响应:', e);
|
||
}
|
||
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
// 获取响应数据
|
||
const buffer = await response.arrayBuffer();
|
||
|
||
// 上传到MinIO
|
||
const fileName = `removed-bg-${Date.now()}.png`;
|
||
const filePath = path.join(os.tmpdir(), fileName);
|
||
|
||
// 将buffer写入临时文件
|
||
fs.writeFileSync(filePath, Buffer.from(buffer));
|
||
|
||
// 创建文件对象
|
||
const fileObject = {
|
||
path: filePath,
|
||
originalname: fileName,
|
||
mimetype: 'image/png'
|
||
};
|
||
|
||
// 上传到MinIO
|
||
const imageUrl = await uploadToMinio(fileObject);
|
||
|
||
// 删除临时文件
|
||
fs.unlinkSync(filePath);
|
||
|
||
// 返回处理结果
|
||
res.json({
|
||
success: true,
|
||
message: '背景去除成功',
|
||
imageUrl: imageUrl
|
||
});
|
||
} catch (error) {
|
||
console.error('Remove.bg API错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// 生成背景处理器
|
||
async generateBgHandler(req, res) {
|
||
try {
|
||
// 检查请求
|
||
if (!req.files || !req.files.base_image) {
|
||
throw new Error('没有上传主体图片');
|
||
}
|
||
|
||
console.log('Received files:', {
|
||
base_image: req.files.base_image[0],
|
||
ref_image: req.files.ref_image ? req.files.ref_image[0] : undefined
|
||
});
|
||
console.log('Received body:', req.body);
|
||
|
||
// 上传主体图片到MinIO
|
||
const baseImageUrl = await uploadToMinio(req.files.base_image[0]);
|
||
console.log('Base image URL:', baseImageUrl);
|
||
|
||
// 如果有参考图片,也上传到MinIO
|
||
let refImageUrl;
|
||
if (req.files.ref_image) {
|
||
refImageUrl = await uploadToMinio(req.files.ref_image[0]);
|
||
console.log('Reference image URL:', refImageUrl);
|
||
}
|
||
|
||
// 获取生成背景预设信息
|
||
let presetPrompt = '';
|
||
try {
|
||
const Setting = mongoose.model('Setting');
|
||
const settings = await Setting.findOne({});
|
||
if (settings && settings.generateBgPreset) {
|
||
presetPrompt = settings.generateBgPreset;
|
||
console.log('使用生成背景预设信息:', presetPrompt);
|
||
}
|
||
} catch (error) {
|
||
console.warn('获取生成背景预设信息失败:', error.message);
|
||
// 继续执行,使用空的预设信息
|
||
}
|
||
|
||
// 合并用户提示词和预设提示词
|
||
const userPrompt = req.body.ref_prompt || '自然风景';
|
||
const combinedPrompt = presetPrompt
|
||
? `${userPrompt}, ${presetPrompt}`
|
||
: userPrompt;
|
||
|
||
console.log('合并后的提示词:', combinedPrompt);
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: process.env.GENERATE_BG_MODEL,
|
||
input: {
|
||
base_image_url: baseImageUrl,
|
||
...(refImageUrl && { ref_image_url: refImageUrl }),
|
||
ref_prompt: combinedPrompt
|
||
},
|
||
parameters: {
|
||
n: 1,
|
||
model_version: process.env.GENERATE_BG_API_VERSION,
|
||
ref_prompt_weight: 0.5
|
||
}
|
||
};
|
||
|
||
console.log('Calling DashScope API with:', JSON.stringify(requestBody, null, 2));
|
||
|
||
// 调用DashScope API
|
||
const response = await fetch(process.env.GENERATE_BG_API_URL, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', {
|
||
status: response.status,
|
||
statusText: response.statusText,
|
||
headers: Object.fromEntries(response.headers.entries()),
|
||
body: errorData
|
||
});
|
||
throw new Error(`DashScope API failed: ${errorData}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('DashScope API Response:', result);
|
||
|
||
// 获取用户ID(如果已登录)
|
||
let userId = null;
|
||
if (req.session && req.session.user && req.session.user._id) {
|
||
userId = req.session.user._id;
|
||
} else if (req.cookies && req.cookies.token) {
|
||
try {
|
||
const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
|
||
userId = decoded._id;
|
||
} catch (err) {
|
||
console.error('解析token失败:', err);
|
||
}
|
||
}
|
||
|
||
// 如果是异步任务,我们需要等待任务完成后再保存图片信息
|
||
if (result.output && result.output.task_id) {
|
||
console.log('异步任务已创建,任务ID:', result.output.task_id);
|
||
|
||
// 在这里我们可以设置一个定时任务来检查任务状态并保存结果
|
||
// 但为了简化,我们先返回任务ID,让前端处理
|
||
} else if (result.output && result.output.url && userId) {
|
||
// 如果是同步任务,直接保存图片信息
|
||
try {
|
||
await saveGeneratedImage({
|
||
imageUrl: result.output.url,
|
||
type: 'generate-bg',
|
||
originalImageUrl: baseImageUrl,
|
||
userId: userId,
|
||
prompt: combinedPrompt,
|
||
taskId: result.request_id,
|
||
metadata: {
|
||
refImageUrl: refImageUrl || null,
|
||
userPrompt: userPrompt
|
||
}
|
||
});
|
||
console.log('已保存生成背景结果到用户图片库');
|
||
} catch (saveError) {
|
||
console.error('保存图片信息失败:', saveError);
|
||
// 继续处理,不影响主流程
|
||
}
|
||
} else {
|
||
console.log('无法保存图片信息,缺少必要信息或用户未登录');
|
||
}
|
||
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('Generate BG Error:', {
|
||
message: error.message,
|
||
stack: error.stack,
|
||
cause: error.cause
|
||
});
|
||
res.status(500).json({
|
||
error: error.message,
|
||
details: {
|
||
stack: error.stack,
|
||
cause: error.cause
|
||
}
|
||
});
|
||
}
|
||
},
|
||
|
||
// 图片分析处理器
|
||
async analyzeImageHandler(req, res) {
|
||
try {
|
||
console.log('开始处理图片分析请求...');
|
||
|
||
if (!req.file) {
|
||
return res.status(400).json({ error: '未上传图片文件' });
|
||
}
|
||
|
||
// 检查文件大小
|
||
const maxSize = 20 * 1024 * 1024; // 20MB
|
||
if (req.file.size > maxSize) {
|
||
return res.status(400).json({ error: `文件大小不能超过20MB` });
|
||
}
|
||
|
||
// 上传图片到 MinIO
|
||
const imageUrl = await uploadToMinio(req.file);
|
||
console.log('Image URL:', imageUrl);
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "qwen-vl-plus",
|
||
messages: [
|
||
{
|
||
role: "user",
|
||
content: [
|
||
{
|
||
type: "image_url",
|
||
image_url: {
|
||
url: imageUrl,
|
||
min_pixels: 28 * 28 * 4,
|
||
max_pixels: 21982 * 21982
|
||
}
|
||
},
|
||
{
|
||
type: "text",
|
||
text: "请详细描述图片中的场景、对象、布局和视觉特征。"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
};
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch(
|
||
'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
||
{
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
}
|
||
);
|
||
|
||
const responseText = await response.text();
|
||
console.log('\n=== API 响应 ===');
|
||
console.log('Response status:', response.status);
|
||
console.log('Raw response:', responseText);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`图片分析失败: ${responseText}`);
|
||
}
|
||
|
||
const result = JSON.parse(responseText);
|
||
console.log('DashScope API Response:', result);
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('\n=== 图片分析错误 ===');
|
||
console.error('Analyze Image Error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
},
|
||
|
||
// 视频分析处理器
|
||
async analyzeVideoHandler(req, res) {
|
||
try {
|
||
console.log('\n=== 开始视频分析 ===');
|
||
const { url } = req.body;
|
||
if (!url) {
|
||
throw new Error('请提供视频链接');
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "qwen-vl-max-latest",
|
||
messages: [
|
||
{
|
||
role: "user",
|
||
content: [
|
||
{
|
||
type: "video",
|
||
video: [url] // 支持多个图片帧
|
||
},
|
||
{
|
||
type: "text",
|
||
text: "请提取视频中的语音内容、文字内容,并分析视频的场景、主题和故事情节。"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
};
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch(
|
||
'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
||
{
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
}
|
||
);
|
||
|
||
const responseText = await response.text();
|
||
console.log('\n=== API 响应 ===');
|
||
console.log('Response status:', response.status);
|
||
console.log('Raw response:', responseText);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`视频分析失败: ${responseText}`);
|
||
}
|
||
|
||
const result = JSON.parse(responseText);
|
||
console.log('DashScope API Response:', result);
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('\n=== 视频分析错误 ===');
|
||
console.error('Analyze Video Error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
},
|
||
|
||
// 网页分析处理器
|
||
async analyzeWebpageHandler(req, res) {
|
||
try {
|
||
console.log('\n=== 开始网页分析 ===');
|
||
const { url } = req.body;
|
||
if (!url) {
|
||
throw new Error('请提供网页链接');
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "qwen-plus",
|
||
messages: [
|
||
{
|
||
role: "system",
|
||
content: "You are a helpful assistant."
|
||
},
|
||
{
|
||
role: "user",
|
||
content: `请分析以下网页内容:${url}\n请提取网页中的主要文字内容、图片内容、表格数据等,并进行内容分析和总结。`,
|
||
}
|
||
],
|
||
extra_body: {
|
||
enable_search: true
|
||
}
|
||
};
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch(
|
||
'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
||
{
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
}
|
||
);
|
||
|
||
const responseText = await response.text();
|
||
console.log('\n=== API 响应 ===');
|
||
console.log('Response status:', response.status);
|
||
console.log('Raw response:', responseText);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`网页分析失败: ${responseText}`);
|
||
}
|
||
|
||
const result = JSON.parse(responseText);
|
||
console.log('DashScope API Response:', result);
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('\n=== 网页分析错误 ===');
|
||
console.error('Analyze Webpage Error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
},
|
||
|
||
// 文字提取处理器
|
||
async extractTextHandler(req, res) {
|
||
try {
|
||
console.log('\n=== 开始文字提取 ===');
|
||
if (!req.file) {
|
||
throw new Error('请上传图片');
|
||
}
|
||
|
||
// 上传图片到 MinIO
|
||
const imageUrl = await uploadToMinio(req.file);
|
||
console.log('Image URL:', imageUrl);
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "qwen-vl-ocr",
|
||
messages: [
|
||
{
|
||
role: "user",
|
||
content: [
|
||
{
|
||
type: "image_url",
|
||
image_url: {
|
||
url: imageUrl,
|
||
min_pixels: 28 * 28 * 4,
|
||
max_pixels: 1280 * 784
|
||
}
|
||
},
|
||
{
|
||
type: "text",
|
||
text: "Read all the text in the image."
|
||
}
|
||
]
|
||
}
|
||
],
|
||
stream: false
|
||
};
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch(
|
||
'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
|
||
{
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
}
|
||
);
|
||
|
||
const responseText = await response.text();
|
||
console.log('\n=== API 响应 ===');
|
||
console.log('Response status:', response.status);
|
||
console.log('Raw response:', responseText);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`文字提取失败: ${responseText}`);
|
||
}
|
||
|
||
const result = JSON.parse(responseText);
|
||
console.log('DashScope API Response:', result);
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('\n=== 文字提取错误 ===');
|
||
console.error('Extract Text Error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
},
|
||
|
||
// 任务状态检查处理器
|
||
async checkTaskHandler(req, res) {
|
||
try {
|
||
const { taskId } = req.params;
|
||
console.log('\n=== 检查任务状态 ===');
|
||
console.log('Task ID:', taskId);
|
||
|
||
const response = await fetch(`https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`
|
||
}
|
||
});
|
||
|
||
console.log('Response status:', response.status);
|
||
const responseText = await response.text();
|
||
console.log('Response text:', responseText);
|
||
|
||
if (!response.ok) {
|
||
console.error('Task check error:', {
|
||
status: response.status,
|
||
statusText: response.statusText,
|
||
body: responseText
|
||
});
|
||
throw new Error('任务状态检查失败');
|
||
}
|
||
|
||
// 尝试解析响应
|
||
let result;
|
||
try {
|
||
// 如果响应是 JSON 字符串
|
||
result = JSON.parse(responseText);
|
||
|
||
// 确保结果对象具有预期的结构
|
||
if (!result.output) {
|
||
result.output = {};
|
||
}
|
||
|
||
// 标准化任务状态
|
||
if (result.output && !result.output.task_status && result.status) {
|
||
result.output.task_status = result.status;
|
||
}
|
||
|
||
// 标准化结果格式
|
||
if (result.output && result.output.task_status === 'SUCCEEDED') {
|
||
// 确保结果数据格式一致
|
||
if (result.output.render_urls && !result.output.results) {
|
||
result.output.results = result.output.render_urls.map(url => ({ url }));
|
||
} else if (result.output.results && Array.isArray(result.output.results)) {
|
||
// 确保每个结果项都有url属性
|
||
result.output.results = result.output.results.map(item => {
|
||
if (typeof item === 'string') {
|
||
return { url: item };
|
||
} else if (item && !item.url && item.image_url) {
|
||
return { url: item.image_url };
|
||
}
|
||
return item;
|
||
});
|
||
}
|
||
}
|
||
} catch (jsonError) {
|
||
console.warn('JSON解析失败,尝试其他格式:', jsonError.message);
|
||
|
||
// 如果响应不是 JSON,检查是否是文档内容
|
||
if (responseText.includes('本文介绍创意海报生成模型')) {
|
||
throw new Error('API 返回了文档内容而不是结果');
|
||
}
|
||
|
||
// 如果响应是 URL
|
||
if (responseText.trim().startsWith('http')) {
|
||
result = {
|
||
output: {
|
||
task_status: 'SUCCEEDED',
|
||
results: [{ url: responseText.trim() }]
|
||
}
|
||
};
|
||
} else {
|
||
// 尝试从文本中提取URL
|
||
const urlMatch = responseText.match(/(https?:\/\/[^\s]+)/g);
|
||
if (urlMatch && urlMatch.length > 0) {
|
||
result = {
|
||
output: {
|
||
task_status: 'SUCCEEDED',
|
||
results: urlMatch.map(url => ({ url }))
|
||
}
|
||
};
|
||
} else {
|
||
console.error('无法从响应中提取有效数据:', responseText);
|
||
throw new Error('无效的响应格式');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查任务状态
|
||
if (result.output && result.output.task_status === 'FAILED') {
|
||
throw new Error(result.output.message || '任务执行失败');
|
||
}
|
||
|
||
// 如果任务成功并且有结果,保存生成的图片信息
|
||
if (result.output && result.output.task_status === 'SUCCEEDED') {
|
||
try {
|
||
// 获取用户ID(如果用户已登录)
|
||
let userId = null;
|
||
if (req.session && req.session.user) {
|
||
userId = req.session.user._id;
|
||
}
|
||
|
||
// 确定图片类型
|
||
let imageType = 'other';
|
||
console.log('正在确定图片类型,任务ID:', taskId);
|
||
if (result.output.render_urls) {
|
||
imageType = 'generate-bg'; // 假设是生成背景
|
||
console.log('检测到render_urls,设置类型为:', imageType);
|
||
} else if (result.output.results && result.output.results[0] && result.output.results[0].url) {
|
||
// 根据URL或其他信息确定类型
|
||
const url = result.output.results[0].url;
|
||
console.log('检测到results[0].url:', url);
|
||
if (url.includes('remove-bg')) {
|
||
imageType = 'remove-bg';
|
||
} else if (url.includes('enhance')) {
|
||
imageType = 'enhance-image';
|
||
} else if (url.includes('extend')) {
|
||
imageType = 'extend-image';
|
||
} else if (taskId.includes('background') || url.includes('dashscope-result')) {
|
||
// 如果任务ID包含background或URL来自dashscope,很可能是生成背景任务
|
||
imageType = 'generate-bg';
|
||
}
|
||
console.log('根据URL判断,设置类型为:', imageType);
|
||
}
|
||
|
||
// 保存每个生成的图片
|
||
if (result.output.render_urls) {
|
||
// 对于海报生成等返回render_urls的情况
|
||
for (const url of result.output.render_urls) {
|
||
await saveGeneratedImage({
|
||
imageUrl: url,
|
||
type: imageType,
|
||
userId,
|
||
taskId
|
||
});
|
||
console.log(`已保存生成的图片信息到数据库: ${imageType}, URL: ${url}`);
|
||
}
|
||
} else if (result.output.results) {
|
||
// 对于返回results数组的情况
|
||
for (const item of result.output.results) {
|
||
// 对于生成背景任务,强制设置类型为generate-bg
|
||
if (taskId.includes('background') || item.url.includes('dashscope-result')) {
|
||
imageType = 'generate-bg';
|
||
}
|
||
await saveGeneratedImage({
|
||
imageUrl: item.url,
|
||
type: imageType,
|
||
userId,
|
||
taskId,
|
||
metadata: item
|
||
});
|
||
console.log(`已保存生成的图片信息到数据库: ${imageType}, URL: ${item.url}`);
|
||
}
|
||
}
|
||
} catch (saveError) {
|
||
console.error('保存生成的图片信息时出错:', saveError);
|
||
// 不抛出错误,继续返回结果
|
||
}
|
||
}
|
||
|
||
console.log('Task check result:', result);
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('Task check error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
},
|
||
|
||
// MinIO 健康检查处理器
|
||
async checkMinioHandler(req, res) {
|
||
try {
|
||
console.log('Checking MinIO connection...');
|
||
console.log('MinIO Config:', {
|
||
endPoint: process.env.MINIO_ENDPOINT,
|
||
port: process.env.MINIO_PORT,
|
||
useSSL: process.env.MINIO_USE_SSL,
|
||
bucketName: process.env.MINIO_BUCKET_NAME
|
||
});
|
||
|
||
// 检查 bucket 是否存在
|
||
const bucketExists = await minioClient.bucketExists(process.env.MINIO_BUCKET_NAME);
|
||
console.log('Bucket exists:', bucketExists);
|
||
|
||
if (!bucketExists) {
|
||
// 尝试创建 bucket
|
||
await minioClient.makeBucket(process.env.MINIO_BUCKET_NAME);
|
||
console.log('Bucket created successfully');
|
||
}
|
||
|
||
// 列出所有 buckets
|
||
const buckets = await minioClient.listBuckets();
|
||
console.log('Available buckets:', buckets);
|
||
|
||
// 尝试生成一个测试文件的 URL
|
||
const testUrl = await minioClient.presignedGetObject(
|
||
process.env.MINIO_BUCKET_NAME,
|
||
'test.txt',
|
||
60 // URL 有效期 60 秒
|
||
);
|
||
console.log('Test URL generated:', testUrl);
|
||
|
||
res.json({
|
||
status: 'ok',
|
||
bucketExists,
|
||
buckets,
|
||
testUrl,
|
||
config: {
|
||
endPoint: process.env.MINIO_ENDPOINT,
|
||
port: process.env.MINIO_PORT,
|
||
useSSL: process.env.MINIO_USE_SSL,
|
||
bucketName: process.env.MINIO_BUCKET_NAME
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('MinIO Check Error:', error);
|
||
res.status(500).json({
|
||
status: 'error',
|
||
error: error.message,
|
||
stack: error.stack
|
||
});
|
||
}
|
||
},
|
||
|
||
// 模特生成处理器
|
||
async generateModelHandler(req, res) {
|
||
try {
|
||
console.log('\n=== 开始模特生成 ===');
|
||
|
||
// 检查请求
|
||
if (!req.files || !req.files.base_image) {
|
||
throw new Error('请上传模特图片');
|
||
}
|
||
|
||
console.log('Received files:', {
|
||
base_image: req.files.base_image[0],
|
||
mask_image: req.files.mask_image ? req.files.mask_image[0] : undefined,
|
||
model_image: req.files.model_image ? req.files.model_image[0] : undefined,
|
||
background_image: req.files.background_image ? req.files.background_image[0] : undefined
|
||
});
|
||
console.log('Received body:', req.body);
|
||
|
||
// 上传主体图片到MinIO
|
||
const baseImageUrl = await uploadToMinio(req.files.base_image[0]);
|
||
console.log('Base image URL:', baseImageUrl);
|
||
|
||
// 如果有遮罩图片,也上传到MinIO
|
||
let maskImageUrl;
|
||
if (req.files.mask_image) {
|
||
maskImageUrl = await uploadToMinio(req.files.mask_image[0]);
|
||
console.log('Mask image URL:', maskImageUrl);
|
||
}
|
||
|
||
// 如果有模特图片,上传到MinIO
|
||
let modelImageUrl;
|
||
if (req.files.model_image) {
|
||
modelImageUrl = await uploadToMinio(req.files.model_image[0]);
|
||
console.log('Model image URL:', modelImageUrl);
|
||
}
|
||
|
||
// 如果有背景图片,上传到MinIO
|
||
let backgroundImageUrl;
|
||
if (req.files.background_image) {
|
||
backgroundImageUrl = await uploadToMinio(req.files.background_image[0]);
|
||
console.log('Background image URL:', backgroundImageUrl);
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: req.body.model_version || 'virtualmodel-v2',
|
||
input: {
|
||
base_image_url: baseImageUrl,
|
||
prompt: req.body.prompt || '一名年轻女子,身穿白色短裤,极简风格调色板',
|
||
face_prompt: req.body.face_prompt || '年轻女子,面容姣好,最高品质'
|
||
},
|
||
parameters: {
|
||
short_side_size: req.body.short_side_size || '1024',
|
||
n: parseInt(req.body.n) || 2
|
||
}
|
||
};
|
||
|
||
// 添加可选图片URL
|
||
if (maskImageUrl) {
|
||
requestBody.input.mask_image_url = maskImageUrl;
|
||
}
|
||
|
||
if (modelImageUrl) {
|
||
requestBody.input.model_image_url = modelImageUrl;
|
||
}
|
||
|
||
if (backgroundImageUrl) {
|
||
requestBody.input.background_image_url = backgroundImageUrl;
|
||
}
|
||
|
||
console.log('Calling DashScope API with:', JSON.stringify(requestBody, null, 2));
|
||
|
||
// 调用DashScope API
|
||
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/virtualmodel/generation', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', {
|
||
status: response.status,
|
||
statusText: response.statusText,
|
||
headers: Object.fromEntries(response.headers.entries()),
|
||
body: errorData
|
||
});
|
||
throw new Error(`DashScope API failed: ${errorData}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('DashScope API Response:', result);
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('Generate Model Error:', {
|
||
message: error.message,
|
||
stack: error.stack,
|
||
cause: error.cause
|
||
});
|
||
res.status(500).json({
|
||
error: error.message,
|
||
details: {
|
||
stack: error.stack,
|
||
cause: error.cause
|
||
}
|
||
});
|
||
}
|
||
},
|
||
|
||
// 图像扩展处理器
|
||
async extendImageHandler(req, res) {
|
||
try {
|
||
const { imageUrl, isBase64, parameters } = req.body;
|
||
|
||
if (!imageUrl) {
|
||
return res.status(400).json({ error: '缺少图片URL' });
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "image-out-painting",
|
||
input: {
|
||
...(isBase64 ? { image_base64: imageUrl.split(',')[1] } : { image_url: imageUrl })
|
||
},
|
||
parameters: parameters || {}
|
||
};
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', errorData);
|
||
throw new Error(`图像扩展API调用失败: ${errorData}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('图像扩展错误:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
},
|
||
|
||
// 图像扩展处理器 - 直接上传图片版本
|
||
async extendImageDirectHandler(req, res) {
|
||
try {
|
||
console.log('\n=== 开始直接上传图片扩展 ===');
|
||
|
||
// 检查是否有文件上传
|
||
if (!req.file) {
|
||
return res.status(400).json({ error: '缺少图片文件' });
|
||
}
|
||
|
||
// 详细记录文件信息
|
||
console.log('上传的文件信息:', {
|
||
fieldname: req.file.fieldname,
|
||
originalname: req.file.originalname,
|
||
encoding: req.file.encoding,
|
||
mimetype: req.file.mimetype,
|
||
size: req.file.size,
|
||
buffer: req.file.buffer ? '存在' : '不存在',
|
||
path: req.file.path || '不存在'
|
||
});
|
||
|
||
// 获取参数
|
||
const mode = req.body.mode || 'outpainting';
|
||
let parameters;
|
||
try {
|
||
parameters = JSON.parse(req.body.parameters || '{}');
|
||
} catch (e) {
|
||
console.error('解析参数错误:', e);
|
||
parameters = {};
|
||
}
|
||
|
||
console.log('扩展模式:', mode);
|
||
console.log('参数:', parameters);
|
||
|
||
let imageUrl;
|
||
try {
|
||
// 上传到MinIO
|
||
const fileObject = {
|
||
buffer: req.file.buffer,
|
||
originalname: req.file.originalname,
|
||
mimetype: req.file.mimetype,
|
||
size: req.file.size
|
||
};
|
||
|
||
imageUrl = await uploadToMinio(fileObject);
|
||
console.log('图片已上传到MinIO:', imageUrl);
|
||
} catch (error) {
|
||
console.error('上传到MinIO失败:', error);
|
||
|
||
// 如果上传失败,将buffer写入临时文件
|
||
const uploadsDir = path.join(__dirname, 'uploads');
|
||
if (!fs.existsSync(uploadsDir)) {
|
||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||
}
|
||
|
||
const fileName = `temp-${Date.now()}-${req.file.originalname}`;
|
||
const localFilePath = path.join(uploadsDir, fileName);
|
||
fs.writeFileSync(localFilePath, req.file.buffer);
|
||
|
||
imageUrl = `${req.protocol}://${req.get('host')}/uploads/${fileName}`;
|
||
console.log('使用本地文件URL:', imageUrl);
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "image-out-painting",
|
||
input: {
|
||
image_url: imageUrl
|
||
},
|
||
parameters: parameters
|
||
};
|
||
|
||
// 根据模式设置参数
|
||
if (mode === 'ratio') {
|
||
// 等比例扩图模式
|
||
requestBody.parameters = {
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false, // 默认为true
|
||
add_watermark: parameters.add_watermark !== false // 默认为true
|
||
};
|
||
} else if (mode === 'direction') {
|
||
// 指定方向扩图模式
|
||
requestBody.parameters = {
|
||
left_offset: parseInt(parameters.left_offset) || 0,
|
||
right_offset: parseInt(parameters.right_offset) || 0,
|
||
top_offset: parseInt(parameters.top_offset) || 0,
|
||
bottom_offset: parseInt(parameters.bottom_offset) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'aspect') {
|
||
// 指定宽高比扩图模式
|
||
requestBody.parameters = {
|
||
output_ratio: parameters.output_ratio || "16:9",
|
||
angle: parseInt(parameters.angle) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'rotate') {
|
||
// 旋转图像模式
|
||
requestBody.parameters = {
|
||
angle: parseInt(parameters.angle) || 45,
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else {
|
||
// 默认使用传入的参数
|
||
requestBody.parameters = parameters;
|
||
}
|
||
|
||
console.log('发送到DashScope的请求体:', JSON.stringify(requestBody, null, 2));
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', errorData);
|
||
|
||
// 尝试解析错误信息
|
||
let errorMessage = `图像扩展API调用失败: ${errorData}`;
|
||
let errorCode = '';
|
||
|
||
try {
|
||
const errorJson = JSON.parse(errorData);
|
||
if (errorJson.code) {
|
||
errorCode = errorJson.code;
|
||
errorMessage = errorJson.message || errorMessage;
|
||
}
|
||
} catch (e) {
|
||
// 解析失败,使用原始错误文本
|
||
}
|
||
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('DashScope API响应:', result);
|
||
|
||
// 清理临时文件
|
||
try {
|
||
fs.unlinkSync(filePath);
|
||
console.log('临时文件已删除:', filePath);
|
||
} catch (error) {
|
||
console.error('删除临时文件失败:', error);
|
||
}
|
||
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('图像扩展错误:', error);
|
||
|
||
// 提供更详细的错误信息
|
||
let statusCode = 500;
|
||
let errorResponse = {
|
||
error: error.message,
|
||
success: false
|
||
};
|
||
|
||
// 如果是文件相关错误
|
||
if (error.code === 'ENOENT') {
|
||
statusCode = 400;
|
||
errorResponse.details = '文件不存在或无法访问';
|
||
} else if (error.message.includes('MinIO')) {
|
||
statusCode = 500;
|
||
errorResponse.details = '文件存储服务错误';
|
||
} else if (error.message.includes('API')) {
|
||
statusCode = 502;
|
||
errorResponse.details = '外部API服务错误';
|
||
}
|
||
|
||
res.status(statusCode).json(errorResponse);
|
||
}
|
||
},
|
||
|
||
// 线稿生图处理器
|
||
async sketchToImageHandler(req, res) {
|
||
try {
|
||
console.log('\n=== 开始线稿生图 ===');
|
||
|
||
// 检查请求
|
||
if (!req.files || !req.files.sketch_image) {
|
||
throw new Error('请上传线稿图片');
|
||
}
|
||
|
||
console.log('Received files:', {
|
||
sketch_image: req.files.sketch_image[0],
|
||
});
|
||
console.log('Received body:', req.body);
|
||
|
||
// 上传线稿图片到MinIO
|
||
const sketchImageUrl = await uploadToMinio(req.files.sketch_image[0]);
|
||
console.log('Sketch image URL:', sketchImageUrl);
|
||
|
||
// 使用腾讯云 SDK
|
||
const tencentcloud = require("tencentcloud-sdk-nodejs");
|
||
const AiartClient = tencentcloud.aiart.v20221229.Client;
|
||
|
||
const clientConfig = {
|
||
credential: {
|
||
secretId: process.env.TC_SECRET_ID,
|
||
secretKey: process.env.TC_SECRET_KEY,
|
||
},
|
||
region: "ap-guangzhou",
|
||
profile: {
|
||
httpProfile: {
|
||
endpoint: "aiart.tencentcloudapi.com",
|
||
},
|
||
},
|
||
};
|
||
|
||
// 实例化要请求产品的client对象
|
||
const client = new AiartClient(clientConfig);
|
||
|
||
// 构造请求参数
|
||
const params = {
|
||
"Prompt": req.body.prompt || '一张黑白线稿图,需要填充色彩与细节',
|
||
"InputUrl": sketchImageUrl,
|
||
"RspImgType": "url",
|
||
"LogoAdd": parseInt(req.body.logoAdd) || 1
|
||
};
|
||
|
||
console.log('Calling Tencent Cloud API with params:', params);
|
||
|
||
// 发起请求并处理响应
|
||
const data = await client.SketchToImage(params);
|
||
console.log('腾讯云 API 响应:', JSON.stringify(data, null, 2));
|
||
|
||
// 检查是否有有效的图片 URL
|
||
if (!data || !data.ResultImage) {
|
||
throw new Error('API 返回的结果中没有有效的图片 URL');
|
||
}
|
||
|
||
// 返回结果
|
||
res.json({
|
||
success: true,
|
||
message: '线稿生图成功',
|
||
data: data
|
||
});
|
||
} catch (error) {
|
||
console.error('线稿生图错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// Tripo3D处理函数
|
||
async tripo3dUploadHandler(req, res) {
|
||
try {
|
||
const tripo3dRouter = require('./routes/tripo3d');
|
||
// 调用路由处理函数
|
||
return tripo3dRouter.handleUpload(req, res);
|
||
} catch (error) {
|
||
console.error('Tripo3D上传错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
async tripo3dGenerateHandler(req, res) {
|
||
try {
|
||
const tripo3dRouter = require('./routes/tripo3d');
|
||
return tripo3dRouter.handleGenerate(req, res);
|
||
} catch (error) {
|
||
console.error('Tripo3D生成错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
async tripo3dTaskHandler(req, res) {
|
||
try {
|
||
const tripo3dRouter = require('./routes/tripo3d');
|
||
return tripo3dRouter.handleTask(req, res);
|
||
} catch (error) {
|
||
console.error('Tripo3D任务查询错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// 视频生成处理器
|
||
async generateVideoHandler(req, res) {
|
||
try {
|
||
console.log('视频生成处理器被调用,请求体:', req.body);
|
||
|
||
// 检查是否有image_url参数
|
||
if (req.body.image_url) {
|
||
console.log('检测到图片URL,使用图片生成视频');
|
||
|
||
// 获取请求参数
|
||
const { model, image_url, prompt, quality, with_audio, size, fps } = req.body;
|
||
|
||
if (!model) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '缺少必要参数: model'
|
||
});
|
||
}
|
||
|
||
if (!image_url) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '缺少必要参数: image_url'
|
||
});
|
||
}
|
||
|
||
// 尝试从URL获取图片并转换为Base64
|
||
try {
|
||
console.log('尝试从URL获取图片:', image_url);
|
||
const imageResponse = await fetch(image_url);
|
||
if (!imageResponse.ok) {
|
||
throw new Error(`获取图片失败: ${imageResponse.status} ${imageResponse.statusText}`);
|
||
}
|
||
|
||
const imageBuffer = await imageResponse.buffer();
|
||
const imageBase64 = imageBuffer.toString('base64');
|
||
const mimeType = imageResponse.headers.get('content-type') || 'image/jpeg';
|
||
const base64ImageUrl = `data:${mimeType};base64,${imageBase64}`;
|
||
|
||
console.log('图片已转换为Base64,长度:', base64ImageUrl.length);
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: model,
|
||
image_url: base64ImageUrl
|
||
};
|
||
|
||
// 添加可选参数
|
||
if (prompt) requestBody.prompt = prompt;
|
||
if (quality) requestBody.quality = quality;
|
||
if (with_audio !== undefined) {
|
||
// 确保with_audio是布尔值
|
||
requestBody.with_audio = with_audio === true || with_audio === 'true';
|
||
}
|
||
if (size) requestBody.size = size;
|
||
if (fps) requestBody.fps = parseInt(fps);
|
||
|
||
// 生成唯一请求ID
|
||
const requestId = uuid.v4();
|
||
requestBody.request_id = requestId;
|
||
|
||
console.log('准备调用智谱AI API,请求体结构:', Object.keys(requestBody));
|
||
|
||
// 调用智谱AI视频生成API
|
||
const ZHIPUAI_API_URL = process.env.ZHIPUAI_API_URL || 'https://open.bigmodel.cn/api/paas/v4';
|
||
const ZHIPUAI_API_KEY = process.env.ZHIPUAI_API_KEY;
|
||
|
||
console.log('使用的API URL:', `${ZHIPUAI_API_URL}/videos/generations`);
|
||
console.log('API密钥前几位:', ZHIPUAI_API_KEY ? ZHIPUAI_API_KEY.substring(0, 5) + '...' : 'undefined');
|
||
|
||
const response = await fetch(`${ZHIPUAI_API_URL}/videos/generations`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${ZHIPUAI_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
console.log('API响应状态:', response.status, response.statusText);
|
||
|
||
const responseText = await response.text();
|
||
console.log('API响应原始文本:', responseText);
|
||
|
||
let result;
|
||
try {
|
||
result = JSON.parse(responseText);
|
||
} catch (e) {
|
||
console.error('解析API响应JSON失败:', e);
|
||
throw new Error('无法解析API响应: ' + responseText);
|
||
}
|
||
|
||
console.log('智谱AI API响应:', JSON.stringify(result));
|
||
|
||
// 检查API响应
|
||
if (!result.id) {
|
||
throw new Error(result.message || '视频生成任务创建失败');
|
||
}
|
||
|
||
return res.json({
|
||
success: true,
|
||
data: result
|
||
});
|
||
} catch (error) {
|
||
console.error('处理图片URL错误:', error);
|
||
return res.status(500).json({
|
||
success: false,
|
||
message: `处理图片URL错误: ${error.message}`
|
||
});
|
||
}
|
||
} else {
|
||
// 使用文本生成视频
|
||
const videoRouter = require('./routes/video');
|
||
return videoRouter.handleGenerateVideo(req, res);
|
||
}
|
||
} catch (error) {
|
||
console.error('视频生成错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// 从图片生成视频处理器
|
||
async generateVideoFromImageHandler(req, res) {
|
||
try {
|
||
console.log('从图片生成视频处理器被调用');
|
||
|
||
// 使用multer处理文件上传
|
||
upload.single('image')(req, res, async (err) => {
|
||
if (err) {
|
||
console.error('文件上传错误:', err);
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: `文件上传错误: ${err.message}`
|
||
});
|
||
}
|
||
|
||
try {
|
||
// 检查是否有文件上传
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '没有上传文件'
|
||
});
|
||
}
|
||
|
||
console.log('文件上传成功:', req.file);
|
||
console.log('请求体:', req.body);
|
||
|
||
// 获取请求参数
|
||
const { model, prompt, quality, with_audio, size, fps } = req.body;
|
||
|
||
if (!model) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '缺少必要参数: model'
|
||
});
|
||
}
|
||
|
||
// 读取上传的文件并转换为Base64
|
||
const imageBuffer = fs.readFileSync(req.file.path);
|
||
const imageBase64 = imageBuffer.toString('base64');
|
||
const mimeType = req.file.mimetype || 'image/jpeg';
|
||
const base64ImageUrl = `data:${mimeType};base64,${imageBase64}`;
|
||
|
||
console.log('图片已转换为Base64,长度:', base64ImageUrl.length);
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: model,
|
||
image_url: base64ImageUrl
|
||
};
|
||
|
||
// 添加可选参数
|
||
if (prompt) requestBody.prompt = prompt;
|
||
if (quality) requestBody.quality = quality;
|
||
if (with_audio !== undefined) {
|
||
// 确保with_audio是布尔值
|
||
requestBody.with_audio = with_audio === true || with_audio === 'true';
|
||
}
|
||
if (size) requestBody.size = size;
|
||
if (fps) requestBody.fps = parseInt(fps);
|
||
|
||
// 生成唯一请求ID
|
||
const requestId = uuid.v4();
|
||
requestBody.request_id = requestId;
|
||
|
||
console.log('准备调用智谱AI API,请求体结构:', Object.keys(requestBody));
|
||
|
||
// 调用智谱AI视频生成API
|
||
const ZHIPUAI_API_URL = process.env.ZHIPUAI_API_URL || 'https://open.bigmodel.cn/api/paas/v4';
|
||
const ZHIPUAI_API_KEY = process.env.ZHIPUAI_API_KEY;
|
||
|
||
console.log('使用的API URL:', `${ZHIPUAI_API_URL}/videos/generations`);
|
||
console.log('API密钥前几位:', ZHIPUAI_API_KEY ? ZHIPUAI_API_KEY.substring(0, 5) + '...' : 'undefined');
|
||
|
||
const response = await fetch(`${ZHIPUAI_API_URL}/videos/generations`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${ZHIPUAI_API_KEY}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
console.log('API响应状态:', response.status, response.statusText);
|
||
|
||
const responseText = await response.text();
|
||
console.log('API响应原始文本:', responseText);
|
||
|
||
let result;
|
||
try {
|
||
result = JSON.parse(responseText);
|
||
} catch (e) {
|
||
console.error('解析API响应JSON失败:', e);
|
||
throw new Error('无法解析API响应: ' + responseText);
|
||
}
|
||
|
||
console.log('智谱AI API响应:', JSON.stringify(result));
|
||
|
||
// 检查API响应
|
||
if (!result.id) {
|
||
throw new Error(result.message || '视频生成任务创建失败');
|
||
}
|
||
|
||
// 删除临时文件
|
||
try {
|
||
fs.unlinkSync(req.file.path);
|
||
console.log('临时文件已删除:', req.file.path);
|
||
} catch (error) {
|
||
console.error('删除临时文件失败:', error);
|
||
}
|
||
|
||
return res.json({
|
||
success: true,
|
||
data: result
|
||
});
|
||
} catch (error) {
|
||
console.error('处理上传图片错误:', error);
|
||
|
||
// 尝试删除临时文件
|
||
if (req.file && req.file.path) {
|
||
try {
|
||
fs.unlinkSync(req.file.path);
|
||
console.log('临时文件已删除:', req.file.path);
|
||
} catch (e) {
|
||
console.error('删除临时文件失败:', e);
|
||
}
|
||
}
|
||
|
||
return res.status(500).json({
|
||
success: false,
|
||
message: `处理上传图片错误: ${error.message}`
|
||
});
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('从图片生成视频错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// 视频任务查询处理器
|
||
async checkVideoTaskHandler(req, res) {
|
||
try {
|
||
const videoRouter = require('./routes/video');
|
||
return videoRouter.handleCheckVideoTask(req, res);
|
||
} catch (error) {
|
||
console.error('视频任务查询错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: error.message
|
||
});
|
||
}
|
||
},
|
||
|
||
// 添加获取用户生成图片列表的API路由
|
||
async getUserGeneratedImages(req, res) {
|
||
try {
|
||
console.log('[获取用户图片] 请求参数:', req.query);
|
||
|
||
// 检查用户是否已登录 - 支持多种认证方式
|
||
let userId = null;
|
||
|
||
// 1. 检查会话认证
|
||
if (req.session && req.session.user && req.session.user._id) {
|
||
userId = req.session.user._id;
|
||
console.log('[获取用户图片] 通过会话认证,用户ID:', userId);
|
||
}
|
||
// 2. 检查令牌认证
|
||
else {
|
||
// 从请求头中获取令牌
|
||
const authHeader = req.headers.authorization;
|
||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||
const token = authHeader.substring(7);
|
||
try {
|
||
// 验证令牌
|
||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||
userId = decoded._id;
|
||
console.log('[获取用户图片] 通过令牌认证,用户ID:', userId);
|
||
} catch (tokenError) {
|
||
console.error('[获取用户图片] 令牌验证失败:', tokenError);
|
||
}
|
||
}
|
||
|
||
// 3. 从cookie中获取令牌
|
||
if (!userId && req.cookies && req.cookies.token) {
|
||
try {
|
||
const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
|
||
userId = decoded._id;
|
||
console.log('[获取用户图片] 通过cookie令牌认证,用户ID:', userId);
|
||
} catch (cookieTokenError) {
|
||
console.error('[获取用户图片] Cookie令牌验证失败:', cookieTokenError);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果所有认证方式都失败,返回未授权错误
|
||
if (!userId) {
|
||
console.error('[获取用户图片] 认证失败,无法获取用户ID');
|
||
return res.status(401).json({
|
||
success: false,
|
||
error: '未登录或会话已过期,请重新登录'
|
||
});
|
||
}
|
||
|
||
const { type, limit } = req.query;
|
||
|
||
let images;
|
||
if (type && type !== 'all') {
|
||
// 获取指定类型的图片
|
||
console.log(`[获取用户图片] 获取类型 ${type} 的图片,限制 ${limit || 10} 张`);
|
||
images = await getGeneratedImagesByUserAndType(userId, type, limit ? parseInt(limit) : 10);
|
||
} else {
|
||
// 获取所有类型的图片
|
||
console.log(`[获取用户图片] 获取所有类型的图片,限制 ${limit || 20} 张`);
|
||
images = await getAllGeneratedImagesByUser(userId, limit ? parseInt(limit) : 20);
|
||
}
|
||
|
||
console.log(`[获取用户图片] 成功获取 ${images.length} 张图片`);
|
||
res.json({ success: true, data: images });
|
||
} catch (error) {
|
||
console.error('[获取用户图片] 获取用户生成图片列表失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
error: `服务器错误: ${error.message}`
|
||
});
|
||
}
|
||
},
|
||
|
||
// 添加通用文件上传处理程序
|
||
async uploadHandler(req, res) {
|
||
try {
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '未上传文件'
|
||
});
|
||
}
|
||
|
||
console.log('收到单文件上传:', {
|
||
filename: req.file.originalname,
|
||
size: req.file.size,
|
||
type: req.file.mimetype
|
||
});
|
||
|
||
// 上传到MinIO并获取URL
|
||
const fileUrl = await uploadToMinio(req.file);
|
||
|
||
console.log('文件上传成功,URL:', fileUrl);
|
||
|
||
// 返回文件URL
|
||
res.json({
|
||
success: true,
|
||
message: '文件上传成功',
|
||
url: fileUrl
|
||
});
|
||
} catch (error) {
|
||
console.error('处理上传文件时出错:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '上传文件失败: ' + error.message
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
// 定义路由列表
|
||
const routes = [
|
||
{ path: '/api/remove-bg', method: 'POST', handler: routeHandlers.removeBgHandler },
|
||
{ path: '/api/remove-bg-v2', method: 'POST', handler: routeHandlers.removeBgHandler }, // 添加v2版本的路由
|
||
{ path: '/api/generate-bg', method: 'POST', handler: routeHandlers.generateBgHandler },
|
||
{ path: '/api/analyze-image', method: 'POST', handler: routeHandlers.analyzeImageHandler },
|
||
{ path: '/api/analyze-video', method: 'POST', handler: routeHandlers.analyzeVideoHandler },
|
||
{ path: '/api/analyze-webpage', method: 'POST', handler: routeHandlers.analyzeWebpageHandler },
|
||
{ path: '/api/extract-text', method: 'POST', handler: routeHandlers.extractTextHandler },
|
||
{ path: '/api/check-task/:taskId', method: 'GET', handler: routeHandlers.checkTaskHandler },
|
||
{ path: '/api/check-minio', method: 'GET', handler: routeHandlers.checkMinioHandler },
|
||
{ path: '/api/generate-model', method: 'POST', handler: routeHandlers.generateModelHandler },
|
||
{ path: '/api/custom-remove-bg', method: 'POST', handler: routeHandlers.removeBgHandler },
|
||
{ path: '/api/extend-image', method: 'POST', handler: routeHandlers.extendImageHandler },
|
||
{ path: '/api/extend-image-direct', method: 'POST', handler: routeHandlers.extendImageDirectHandler },
|
||
{ path: '/api/generate-video', method: 'POST', handler: routeHandlers.generateVideoHandler },
|
||
{ path: '/api/generate-video-from-image', method: 'POST', handler: routeHandlers.generateVideoFromImageHandler },
|
||
{ path: '/api/check-video-task/:taskId', method: 'GET', handler: routeHandlers.checkVideoTaskHandler },
|
||
{ method: 'POST', path: '/api/tripo3d/upload', handler: routeHandlers.tripo3dUploadHandler },
|
||
{ method: 'POST', path: '/api/tripo3d/generate', handler: routeHandlers.tripo3dGenerateHandler },
|
||
{ method: 'GET', path: '/api/tripo3d/task/:taskId', handler: routeHandlers.tripo3dTaskHandler },
|
||
// 添加获取用户生成图片列表的API路由
|
||
{ path: '/api/user/generated-images', method: 'GET', handler: routeHandlers.getUserGeneratedImages },
|
||
// 添加通用文件上传路由
|
||
{ path: '/api/upload', method: 'POST', handler: routeHandlers.uploadHandler },
|
||
// 添加测试上传路由
|
||
{ path: '/api/test-upload', method: 'POST', handler: async (req, res) => {
|
||
console.log('测试上传路由被调用');
|
||
|
||
// 创建一个简单的multer实例
|
||
const testUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: { fileSize: 5 * 1024 * 1024 } // 5MB
|
||
}).single('testImage');
|
||
|
||
testUpload(req, res, async function(err) {
|
||
if (err) {
|
||
console.error('测试上传错误:', err);
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '文件上传错误: ' + err.message
|
||
});
|
||
}
|
||
|
||
// 检查文件是否存在
|
||
if (!req.file) {
|
||
console.error('未找到上传的文件');
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '未上传文件'
|
||
});
|
||
}
|
||
|
||
// 记录文件信息
|
||
console.log('测试上传成功:', {
|
||
originalname: req.file.originalname,
|
||
mimetype: req.file.mimetype,
|
||
size: req.file.size,
|
||
fieldname: req.file.fieldname
|
||
});
|
||
|
||
try {
|
||
// 尝试上传到MinIO
|
||
const fileUrl = await uploadToMinio(req.file);
|
||
console.log('MinIO上传成功,URL:', fileUrl);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '文件上传成功',
|
||
file: {
|
||
name: req.file.originalname,
|
||
type: req.file.mimetype,
|
||
size: req.file.size
|
||
},
|
||
url: fileUrl
|
||
});
|
||
} catch (error) {
|
||
console.error('MinIO上传错误:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '文件上传到MinIO失败: ' + error.message
|
||
});
|
||
}
|
||
});
|
||
}}
|
||
];
|
||
|
||
// 注册路由
|
||
routes.forEach(route => {
|
||
const { path, method, handler } = route;
|
||
if (path === '/api/generate-bg') {
|
||
// 背景生成路由使用多文件上传
|
||
app[method.toLowerCase()](path, (req, res, next) => {
|
||
multiUpload(req, res, (err) => {
|
||
if (err) {
|
||
next(err);
|
||
return;
|
||
}
|
||
console.log('Received generate-bg request:', {
|
||
files: req.files,
|
||
body: req.body
|
||
});
|
||
handler(req, res).catch(next);
|
||
});
|
||
});
|
||
} else if (path === '/api/test-upload') {
|
||
// 测试上传路由直接使用处理器
|
||
app[method.toLowerCase()](path, handler);
|
||
} else if (path === '/api/generate-video-from-image') {
|
||
// 视频生成路由直接使用处理器
|
||
app[method.toLowerCase()](path, handler);
|
||
} else if (path === '/api/upload') {
|
||
// 通用上传路由使用file字段的单文件上传
|
||
app[method.toLowerCase()](path, (req, res, next) => {
|
||
fileUpload(req, res, (err) => {
|
||
if (err) {
|
||
console.error('文件上传错误:', err);
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '文件上传错误: ' + err.message
|
||
});
|
||
}
|
||
handler(req, res).catch(next);
|
||
});
|
||
});
|
||
} else if (method === 'POST') {
|
||
// 其他 POST 路由使用单文件上传
|
||
app[method.toLowerCase()](path, (req, res, next) => {
|
||
singleUpload(req, res, (err) => {
|
||
if (err) {
|
||
next(err);
|
||
return;
|
||
}
|
||
handler(req, res).catch(next);
|
||
});
|
||
});
|
||
} else {
|
||
// GET 等其他方法直接使用处理器
|
||
app[method.toLowerCase()](path, handler);
|
||
}
|
||
console.log(`✅ 注册路由成功: ${method} ${path}`);
|
||
});
|
||
|
||
// 注册线稿生图路由
|
||
app.post('/api/sketch-to-image', (req, res, next) => {
|
||
const sketchUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: { fileSize: 20 * 1024 * 1024 }
|
||
}).fields([
|
||
{ name: 'sketch_image', maxCount: 1 }
|
||
]);
|
||
|
||
sketchUpload(req, res, (err) => {
|
||
if (err) {
|
||
next(err);
|
||
return;
|
||
}
|
||
routeHandlers.sketchToImageHandler(req, res).catch(next);
|
||
});
|
||
});
|
||
|
||
// 上传文件到MinIO
|
||
async function uploadToMinio(file) {
|
||
try {
|
||
// 生成唯一文件名
|
||
const timestamp = Date.now();
|
||
const extension = file.originalname.split('.').pop();
|
||
const filename = `${timestamp}-${Math.floor(Math.random() * 10000)}.${extension}`;
|
||
const objectName = `uploads/${filename}`;
|
||
|
||
console.log(`准备上传文件到 MinIO: ${objectName}`);
|
||
|
||
// 检查文件对象类型
|
||
let fileData;
|
||
let fileSize;
|
||
|
||
if (file.buffer) {
|
||
// 如果是内存中的文件(multer memory storage)
|
||
fileData = file.buffer;
|
||
fileSize = file.size;
|
||
} else if (file.path) {
|
||
// 如果是磁盘上的文件(multer disk storage)
|
||
fileData = fs.createReadStream(file.path);
|
||
const stats = fs.statSync(file.path);
|
||
fileSize = stats.size;
|
||
} else {
|
||
throw new Error('无效的文件对象,缺少buffer或path属性');
|
||
}
|
||
|
||
// 上传文件
|
||
await minioClient.putObject(
|
||
process.env.MINIO_BUCKET_NAME,
|
||
objectName,
|
||
fileData,
|
||
fileSize,
|
||
{ 'Content-Type': file.mimetype }
|
||
);
|
||
|
||
// 生成访问URL
|
||
const fileUrl = `http://${process.env.MINIO_ENDPOINT}:${process.env.MINIO_PORT}/${process.env.MINIO_BUCKET_NAME}/${objectName}`;
|
||
console.log(`文件上传成功,URL: ${fileUrl}`);
|
||
|
||
return fileUrl;
|
||
} catch (error) {
|
||
console.error('上传到 MinIO 失败:', error);
|
||
throw new Error(`上传到 MinIO 失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// MinIO 测试上传接口
|
||
app.post('/api/test-minio-upload', (req, res, next) => {
|
||
singleUpload(req, res, (err) => {
|
||
if (err) {
|
||
next(err);
|
||
return;
|
||
}
|
||
|
||
(async () => {
|
||
try {
|
||
if (!req.file) {
|
||
throw new Error('No file uploaded');
|
||
}
|
||
|
||
console.log('Testing MinIO upload with file:', {
|
||
filename: req.file.originalname,
|
||
size: req.file.size,
|
||
mimetype: req.file.mimetype
|
||
});
|
||
|
||
// 使用上传函数
|
||
const url = await uploadToMinio(req.file);
|
||
console.log('File uploaded successfully, URL:', url);
|
||
|
||
// 尝试访问上传的文件
|
||
const response = await fetch(url);
|
||
if (!response.ok) {
|
||
throw new Error(`Failed to access uploaded file: ${response.status} ${response.statusText}`);
|
||
}
|
||
|
||
res.json({
|
||
status: 'ok',
|
||
message: 'File uploaded and accessible',
|
||
url,
|
||
fileInfo: {
|
||
filename: req.file.originalname,
|
||
size: req.file.size,
|
||
mimetype: req.file.mimetype
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('MinIO Upload Test Error:', error);
|
||
res.status(500).json({
|
||
status: 'error',
|
||
error: error.message,
|
||
stack: error.stack
|
||
});
|
||
}
|
||
})().catch(next);
|
||
});
|
||
});
|
||
|
||
// 添加图片上传和重绘API路由
|
||
app.post('/api/upload-images', (req, res, next) => {
|
||
const imagesUpload = multer({
|
||
storage: multer.memoryStorage(),
|
||
limits: { fileSize: 20 * 1024 * 1024 }
|
||
}).fields([
|
||
{ name: 'original_image', maxCount: 1 },
|
||
{ name: 'mask_image', maxCount: 1 }
|
||
]);
|
||
|
||
imagesUpload(req, res, (err) => {
|
||
if (err) {
|
||
next(err);
|
||
return;
|
||
}
|
||
|
||
(async () => {
|
||
try {
|
||
const originalImage = req.files['original_image'][0];
|
||
const maskImage = req.files['mask_image'][0];
|
||
|
||
console.log('Received images:', {
|
||
original: {
|
||
size: originalImage.size,
|
||
type: originalImage.mimetype,
|
||
buffer: originalImage.buffer.length
|
||
},
|
||
mask: {
|
||
size: maskImage.size,
|
||
type: maskImage.mimetype,
|
||
buffer: maskImage.buffer.length
|
||
}
|
||
});
|
||
|
||
// 上传到MinIO并获取URL
|
||
const originalUrl = await uploadToMinio(originalImage);
|
||
const maskUrl = await uploadToMinio(maskImage);
|
||
|
||
console.log('Uploaded URLs:', {
|
||
originalUrl,
|
||
maskUrl
|
||
});
|
||
|
||
res.json({ originalUrl, maskUrl });
|
||
} catch (error) {
|
||
console.error('Upload Error:', error);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
})().catch(next);
|
||
});
|
||
});
|
||
|
||
app.post('/api/repaint', async (req, res) => {
|
||
try {
|
||
// 验证请求体
|
||
const { input, parameters } = req.body;
|
||
|
||
if (!input || !input.prompt || !input.base_image_url || !input.mask_image_url) {
|
||
throw new Error('缺少必要的参数:prompt、base_image_url 或 mask_image_url');
|
||
}
|
||
|
||
console.log('\n===== 重绘API请求 =====');
|
||
console.log('关键词:', input.prompt);
|
||
console.log('原图URL:', input.base_image_url);
|
||
console.log('掩码URL:', input.mask_image_url);
|
||
console.log('参数:', parameters);
|
||
|
||
// 验证尺寸参数
|
||
if (parameters && parameters.size) {
|
||
// 确保尺寸格式正确
|
||
const sizePattern = /^(\d+)\*(\d+)$/;
|
||
const sizeMatch = parameters.size.match(sizePattern);
|
||
|
||
if (!sizeMatch) {
|
||
console.warn('尺寸格式不正确,使用默认值 1024*1024');
|
||
parameters.size = '1024*1024';
|
||
} else {
|
||
const width = parseInt(sizeMatch[1]);
|
||
const height = parseInt(sizeMatch[2]);
|
||
|
||
// 检查尺寸是否在合理范围内
|
||
const MAX_DIMENSION = 1024;
|
||
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
||
console.warn(`尺寸超过最大限制 ${MAX_DIMENSION}*${MAX_DIMENSION},将按比例调整`);
|
||
const aspectRatio = width / height;
|
||
|
||
if (width > height) {
|
||
const newWidth = MAX_DIMENSION;
|
||
const newHeight = Math.round(MAX_DIMENSION / aspectRatio);
|
||
parameters.size = `${newWidth}*${newHeight}`;
|
||
} else {
|
||
const newHeight = MAX_DIMENSION;
|
||
const newWidth = Math.round(MAX_DIMENSION * aspectRatio);
|
||
parameters.size = `${newWidth}*${newHeight}`;
|
||
}
|
||
|
||
console.log('调整后的尺寸:', parameters.size);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 验证尺寸参数
|
||
try {
|
||
if (parameters && parameters.size) {
|
||
// 确保尺寸格式正确
|
||
const sizePattern = /^(\d+)\*(\d+)$/;
|
||
const sizeMatch = parameters.size.match(sizePattern);
|
||
|
||
if (!sizeMatch) {
|
||
console.warn('尺寸格式不正确,使用默认值 1024*1024');
|
||
parameters.size = '1024*1024';
|
||
} else {
|
||
const width = parseInt(sizeMatch[1]);
|
||
const height = parseInt(sizeMatch[2]);
|
||
|
||
// 检查数值是否有效
|
||
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
|
||
console.warn('尺寸数值无效,使用默认值 1024*1024');
|
||
parameters.size = '1024*1024';
|
||
} else {
|
||
// 检查尺寸是否在合理范围内
|
||
const MAX_DIMENSION = 1024;
|
||
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
||
console.warn(`尺寸超过最大限制 ${MAX_DIMENSION}*${MAX_DIMENSION},将按比例调整`);
|
||
const aspectRatio = width / height;
|
||
|
||
if (width > height) {
|
||
const newWidth = MAX_DIMENSION;
|
||
const newHeight = Math.round(MAX_DIMENSION / aspectRatio);
|
||
parameters.size = `${newWidth}*${newHeight}`;
|
||
} else {
|
||
const newHeight = MAX_DIMENSION;
|
||
const newWidth = Math.round(MAX_DIMENSION * aspectRatio);
|
||
parameters.size = `${newWidth}*${newHeight}`;
|
||
}
|
||
|
||
console.log('调整后的尺寸:', parameters.size);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// 如果没有提供尺寸参数,使用默认值
|
||
if (parameters) {
|
||
parameters.size = '1024*1024';
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('处理尺寸参数时出错:', error);
|
||
// 出错时使用默认尺寸
|
||
if (parameters) {
|
||
parameters.size = '1024*1024';
|
||
}
|
||
}
|
||
|
||
// 验证尺寸参数
|
||
try {
|
||
if (parameters && parameters.size) {
|
||
// 根据API文档,只支持三种尺寸选项:1024*1024, 720*1280, 1280*720
|
||
const validSizes = ['1024*1024', '720*1280', '1280*720'];
|
||
|
||
if (!validSizes.includes(parameters.size)) {
|
||
console.warn(`尺寸 ${parameters.size} 不是API支持的选项,使用默认值 1024*1024`);
|
||
parameters.size = '1024*1024';
|
||
} else {
|
||
console.log('使用API支持的尺寸:', parameters.size);
|
||
}
|
||
} else {
|
||
// 如果没有提供尺寸参数,使用默认值
|
||
if (parameters) {
|
||
parameters.size = '1024*1024';
|
||
console.log('未提供尺寸参数,使用默认值:', parameters.size);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('处理尺寸参数时出错:', error);
|
||
// 出错时使用默认尺寸
|
||
if (parameters) {
|
||
parameters.size = '1024*1024';
|
||
}
|
||
}
|
||
|
||
// 构建API请求体
|
||
const apiRequestBody = {
|
||
model: 'wanx-x-painting',
|
||
input: {
|
||
prompt: input.prompt,
|
||
base_image_url: input.base_image_url,
|
||
mask_image_url: input.mask_image_url
|
||
},
|
||
parameters: parameters || {
|
||
size: '1024*1024',
|
||
n: 1
|
||
}
|
||
};
|
||
|
||
console.log('发送到DashScope的请求体:', JSON.stringify(apiRequestBody, null, 2));
|
||
|
||
// 调用DashScope API
|
||
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.REPAINT_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(apiRequestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', {
|
||
status: response.status,
|
||
statusText: response.statusText,
|
||
headers: response.headers,
|
||
body: errorData
|
||
});
|
||
throw new Error(`重绘API调用失败: ${errorData}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('DashScope API Response:', result);
|
||
|
||
// 获取任务ID并轮询结果
|
||
const taskId = result.output.task_id;
|
||
|
||
// 轮询任务结果
|
||
let taskResult;
|
||
let attempts = 0;
|
||
const maxAttempts = 30; // 最多等待30次
|
||
|
||
while (attempts < maxAttempts) {
|
||
const taskResponse = await fetch(`https://dashscope.aliyuncs.com/api/v1/tasks/${taskId}`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.REPAINT_API_KEY}`
|
||
}
|
||
});
|
||
|
||
if (!taskResponse.ok) {
|
||
const errorData = await taskResponse.json();
|
||
console.error('Task check error:', errorData);
|
||
throw new Error('任务状态检查失败');
|
||
}
|
||
|
||
taskResult = await taskResponse.json();
|
||
console.log('Task check result:', taskResult);
|
||
|
||
if (taskResult.output.task_status === 'SUCCEEDED') {
|
||
break;
|
||
} else if (taskResult.output.task_status === 'FAILED') {
|
||
throw new Error(taskResult.output.message || '任务执行失败');
|
||
}
|
||
|
||
attempts++;
|
||
await new Promise(resolve => setTimeout(resolve, 2000)); // 等待2秒
|
||
}
|
||
|
||
if (attempts >= maxAttempts) {
|
||
throw new Error('任务执行超时');
|
||
}
|
||
|
||
res.json(taskResult);
|
||
} catch (error) {
|
||
console.error('Repaint Error:', error);
|
||
res.status(500).json({
|
||
error: error.message,
|
||
details: error.stack
|
||
});
|
||
}
|
||
});
|
||
|
||
// 创建抠图任务
|
||
async function createSegmentationTask(apiKey, imageUrl, type, format) {
|
||
const formData = new FormData();
|
||
formData.append('image_url', imageUrl);
|
||
|
||
// 根据类型映射
|
||
if (type === 'person') {
|
||
formData.append('type', 'person');
|
||
} else if (type === 'product' || type === 'general') {
|
||
formData.append('type', 'object');
|
||
} else if (type === 'car') {
|
||
formData.append('type', 'object'); // API没有专门的car类型,使用object
|
||
} else if (type === 'animal') {
|
||
formData.append('type', 'object'); // API没有专门的animal类型,使用object
|
||
}
|
||
|
||
// 设置输出格式
|
||
formData.append('format', format);
|
||
if (format === 'jpg') {
|
||
formData.append('bg_color', 'ffffff'); // 白色背景
|
||
}
|
||
|
||
// 设置返回类型
|
||
formData.append('return_type', '1'); // 返回图片URL
|
||
formData.append('output_type', '2'); // 只返回图片
|
||
|
||
const response = await fetch('https://techsz.aoscdn.com/api/tasks/visual/segmentation', {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-API-KEY': apiKey
|
||
},
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(`创建抠图任务失败: ${JSON.stringify(errorData)}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
if (!result.data || !result.data.task_id) {
|
||
throw new Error('未获取到任务ID');
|
||
}
|
||
|
||
return result.data.task_id;
|
||
}
|
||
|
||
// 轮询获取任务结果
|
||
async function pollTaskResult(apiKey, taskId, maxAttempts = 30, delay = 2000) {
|
||
let attempts = 0;
|
||
|
||
while (attempts < maxAttempts) {
|
||
try {
|
||
const response = await fetch(`https://techsz.aoscdn.com/api/tasks/visual/segmentation/${taskId}`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'X-API-KEY': apiKey
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(`获取任务结果失败: ${JSON.stringify(errorData)}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
|
||
if (!result.data) {
|
||
throw new Error('未获取到任务数据');
|
||
}
|
||
|
||
const { progress, state } = result.data;
|
||
|
||
// 任务失败
|
||
if (state < 0) {
|
||
throw new Error(`任务处理失败: ${JSON.stringify(result)}`);
|
||
}
|
||
|
||
// 任务完成
|
||
if (progress >= 100) {
|
||
return result;
|
||
}
|
||
|
||
// 等待下一次轮询
|
||
console.log(`任务进度: ${progress}%, 继续等待...`);
|
||
|
||
} catch (error) {
|
||
console.error('轮询任务出错:', error);
|
||
// 如果是明确的错误,直接抛出
|
||
if (error.message.includes('任务处理失败') || error.message.includes('获取任务结果失败')) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 等待一段时间后再次尝试
|
||
await new Promise(resolve => setTimeout(resolve, delay));
|
||
attempts++;
|
||
}
|
||
|
||
throw new Error('任务处理超时,请稍后重试');
|
||
}
|
||
|
||
// 添加请求日志中间件
|
||
app.use((req, res, next) => {
|
||
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
|
||
next();
|
||
});
|
||
|
||
// 添加详细的404处理中间件
|
||
app.use((req, res, next) => {
|
||
console.error('\n=== 404错误 ===');
|
||
console.error('请求路径:', req.path);
|
||
console.error('请求方法:', req.method);
|
||
console.error('请求头:', req.headers);
|
||
|
||
res.status(404).json({
|
||
error: '未找到请求的资源',
|
||
path: req.path,
|
||
method: req.method,
|
||
time: new Date().toISOString()
|
||
});
|
||
});
|
||
|
||
function isPortAvailable(port) {
|
||
return new Promise((resolve) => {
|
||
const server = net.createServer();
|
||
server.once('error', () => resolve(false));
|
||
server.once('listening', () => {
|
||
server.close();
|
||
resolve(true);
|
||
});
|
||
server.listen(port);
|
||
});
|
||
}
|
||
|
||
async function findAvailablePort(startPort) {
|
||
let port = startPort;
|
||
while (!(await isPortAvailable(port))) {
|
||
port++;
|
||
}
|
||
return port;
|
||
}
|
||
|
||
// 启动服务器
|
||
async function startMainServer() {
|
||
// Use the findAvailablePort function to get an available port
|
||
const PORT = await findAvailablePort(parseInt(process.env.PORT) || 8000);
|
||
|
||
const server = app.listen(PORT, () => {
|
||
console.log(`服务器运行在端口 ${PORT}`);
|
||
|
||
// 触发服务器ready事件
|
||
app.emit('ready');
|
||
});
|
||
}
|
||
|
||
// Call the async function to start the server
|
||
startMainServer().catch(err => {
|
||
console.error('启动服务器失败:', err);
|
||
process.exit(1);
|
||
});
|
||
|
||
// 全局错误处理中间件
|
||
app.use((err, req, res, next) => {
|
||
console.error('全局错误处理:', err);
|
||
|
||
// 处理 multer 错误
|
||
if (err instanceof multer.MulterError) {
|
||
if (err.code === 'LIMIT_FILE_SIZE') {
|
||
return res.status(413).json({
|
||
error: '文件太大',
|
||
message: `文件大小超过限制 (${MAX_FILE_SIZE / 1024 / 1024}MB)`
|
||
});
|
||
}
|
||
return res.status(400).json({
|
||
error: '文件上传错误',
|
||
message: err.message
|
||
});
|
||
}
|
||
|
||
// 处理其他错误
|
||
res.status(500).json({
|
||
error: '服务器错误',
|
||
message: err.message
|
||
});
|
||
});
|
||
|
||
// 添加图像拓展直接上传路由
|
||
app.post('/api/extend-image-direct', upload.single('image'), async (req, res) => {
|
||
try {
|
||
console.log('\n=== 开始直接上传图片扩展 ===');
|
||
|
||
// 检查是否有文件上传
|
||
if (!req.file) {
|
||
console.error('没有文件被上传');
|
||
return res.status(400).json({ error: '缺少图片文件' });
|
||
}
|
||
|
||
// 详细记录文件信息
|
||
console.log('上传的文件信息:', {
|
||
fieldname: req.file.fieldname,
|
||
originalname: req.file.originalname,
|
||
encoding: req.file.encoding,
|
||
mimetype: req.file.mimetype,
|
||
destination: req.file.destination,
|
||
filename: req.file.filename,
|
||
path: req.file.path,
|
||
size: req.file.size
|
||
});
|
||
|
||
// 获取参数
|
||
const mode = req.body.mode || 'outpainting';
|
||
let parameters;
|
||
try {
|
||
parameters = JSON.parse(req.body.parameters || '{}');
|
||
} catch (e) {
|
||
console.error('解析参数错误:', e);
|
||
parameters = {};
|
||
}
|
||
|
||
console.log('扩展模式:', mode);
|
||
console.log('参数:', parameters);
|
||
|
||
// 上传图片到MinIO获取URL
|
||
const filePath = req.file.path;
|
||
console.log('上传的文件路径:', filePath);
|
||
|
||
if (!filePath) {
|
||
console.error('文件路径为空');
|
||
return res.status(400).json({ error: '文件上传失败,路径为空' });
|
||
}
|
||
|
||
// 检查文件是否存在
|
||
if (!fs.existsSync(filePath)) {
|
||
console.error('文件不存在:', filePath);
|
||
return res.status(400).json({ error: '上传的文件不存在' });
|
||
}
|
||
|
||
let imageUrl;
|
||
try {
|
||
// 上传到MinIO
|
||
const fileObject = {
|
||
path: filePath,
|
||
originalname: req.file.originalname,
|
||
mimetype: req.file.mimetype
|
||
};
|
||
|
||
imageUrl = await uploadToMinio(fileObject);
|
||
console.log('图片已上传到MinIO:', imageUrl);
|
||
} catch (error) {
|
||
console.error('上传到MinIO失败:', error);
|
||
// 如果上传失败,使用本地文件路径
|
||
const fileName = path.basename(filePath);
|
||
imageUrl = `${req.protocol}://${req.get('host')}/uploads/${fileName}`;
|
||
console.log('使用本地文件URL:', imageUrl);
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "image-out-painting",
|
||
input: {
|
||
image_url: imageUrl
|
||
},
|
||
parameters: parameters
|
||
};
|
||
|
||
// 根据模式设置参数
|
||
if (mode === 'ratio') {
|
||
// 等比例扩图模式
|
||
requestBody.parameters = {
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false, // 默认为true
|
||
add_watermark: parameters.add_watermark !== false // 默认为true
|
||
};
|
||
} else if (mode === 'direction') {
|
||
// 指定方向扩图模式
|
||
requestBody.parameters = {
|
||
left_offset: parseInt(parameters.left_offset) || 0,
|
||
right_offset: parseInt(parameters.right_offset) || 0,
|
||
top_offset: parseInt(parameters.top_offset) || 0,
|
||
bottom_offset: parseInt(parameters.bottom_offset) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'aspect') {
|
||
// 指定宽高比扩图模式
|
||
requestBody.parameters = {
|
||
output_ratio: parameters.output_ratio || "16:9",
|
||
angle: parseInt(parameters.angle) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'rotate') {
|
||
// 旋转图像模式
|
||
requestBody.parameters = {
|
||
angle: parseInt(parameters.angle) || 45,
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else {
|
||
// 默认使用传入的参数
|
||
requestBody.parameters = parameters;
|
||
}
|
||
|
||
console.log('发送到DashScope的请求体:', JSON.stringify(requestBody, null, 2));
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', errorData);
|
||
|
||
// 尝试解析错误信息
|
||
let errorMessage = `图像扩展API调用失败: ${errorData}`;
|
||
let errorCode = '';
|
||
|
||
try {
|
||
const errorJson = JSON.parse(errorData);
|
||
if (errorJson.code) {
|
||
errorCode = errorJson.code;
|
||
errorMessage = errorJson.message || errorMessage;
|
||
}
|
||
} catch (e) {
|
||
// 解析失败,使用原始错误文本
|
||
}
|
||
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('DashScope API响应:', result);
|
||
|
||
// 清理临时文件
|
||
try {
|
||
fs.unlinkSync(filePath);
|
||
console.log('临时文件已删除:', filePath);
|
||
} catch (error) {
|
||
console.error('删除临时文件失败:', error);
|
||
}
|
||
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('图像扩展错误:', error);
|
||
|
||
// 提供更详细的错误信息
|
||
let statusCode = 500;
|
||
let errorResponse = {
|
||
error: error.message,
|
||
success: false
|
||
};
|
||
|
||
// 如果是文件相关错误
|
||
if (error.code === 'ENOENT') {
|
||
statusCode = 400;
|
||
errorResponse.details = '文件不存在或无法访问';
|
||
} else if (error.message.includes('MinIO')) {
|
||
statusCode = 500;
|
||
errorResponse.details = '文件存储服务错误';
|
||
} else if (error.message.includes('API')) {
|
||
statusCode = 502;
|
||
errorResponse.details = '外部API服务错误';
|
||
}
|
||
|
||
res.status(statusCode).json(errorResponse);
|
||
}
|
||
});
|
||
|
||
// 启动服务器
|
||
startMainServer().catch(err => {
|
||
console.error('启动服务器失败:', err);
|
||
process.exit(1);
|
||
});
|
||
|
||
// 删除旧的图像拓展直接上传路由,添加新的路由
|
||
app.post('/api/extend-image-direct', (req, res, next) => {
|
||
singleUpload(req, res, (err) => {
|
||
if (err) {
|
||
console.error('文件上传错误:', err);
|
||
return res.status(400).json({
|
||
error: '文件上传失败',
|
||
message: err.message
|
||
});
|
||
}
|
||
next();
|
||
});
|
||
}, async (req, res) => {
|
||
try {
|
||
console.log('\n=== 开始直接上传图片扩展 ===');
|
||
|
||
// 检查是否有文件上传
|
||
if (!req.file) {
|
||
return res.status(400).json({ error: '缺少图片文件' });
|
||
}
|
||
|
||
// 获取参数
|
||
const mode = req.body.mode || 'outpainting';
|
||
let parameters;
|
||
try {
|
||
parameters = JSON.parse(req.body.parameters || '{}');
|
||
} catch (e) {
|
||
console.error('解析参数错误:', e);
|
||
parameters = {};
|
||
}
|
||
|
||
console.log('扩展模式:', mode);
|
||
console.log('参数:', parameters);
|
||
|
||
// 上传图片到MinIO获取URL
|
||
const filePath = req.file.path;
|
||
console.log('上传的文件路径:', filePath);
|
||
|
||
let imageUrl;
|
||
try {
|
||
// 上传到MinIO
|
||
const fileObject = {
|
||
path: filePath,
|
||
originalname: req.file.originalname,
|
||
mimetype: req.file.mimetype
|
||
};
|
||
|
||
imageUrl = await uploadToMinio(fileObject);
|
||
console.log('图片已上传到MinIO:', imageUrl);
|
||
} catch (error) {
|
||
console.error('上传到MinIO失败:', error);
|
||
// 如果上传失败,使用本地文件路径
|
||
const fileName = path.basename(filePath);
|
||
imageUrl = `${req.protocol}://${req.get('host')}/uploads/${fileName}`;
|
||
console.log('使用本地文件URL:', imageUrl);
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "image-out-painting",
|
||
input: {
|
||
image_url: imageUrl
|
||
},
|
||
parameters: parameters
|
||
};
|
||
|
||
// 根据模式设置参数
|
||
if (mode === 'ratio') {
|
||
// 等比例扩图模式
|
||
requestBody.parameters = {
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false, // 默认为true
|
||
add_watermark: parameters.add_watermark !== false // 默认为true
|
||
};
|
||
} else if (mode === 'direction') {
|
||
// 指定方向扩图模式
|
||
requestBody.parameters = {
|
||
left_offset: parseInt(parameters.left_offset) || 0,
|
||
right_offset: parseInt(parameters.right_offset) || 0,
|
||
top_offset: parseInt(parameters.top_offset) || 0,
|
||
bottom_offset: parseInt(parameters.bottom_offset) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'aspect') {
|
||
// 指定宽高比扩图模式
|
||
requestBody.parameters = {
|
||
output_ratio: parameters.output_ratio || "16:9",
|
||
angle: parseInt(parameters.angle) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'rotate') {
|
||
// 旋转图像模式
|
||
requestBody.parameters = {
|
||
angle: parseInt(parameters.angle) || 45,
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else {
|
||
// 默认使用传入的参数
|
||
requestBody.parameters = parameters;
|
||
}
|
||
|
||
console.log('发送到DashScope的请求体:', JSON.stringify(requestBody, null, 2));
|
||
|
||
// 调用 DashScope API
|
||
const response = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${process.env.GENERATE_BG_API_KEY}`,
|
||
'Content-Type': 'application/json',
|
||
'X-DashScope-Async': 'enable'
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.text();
|
||
console.error('DashScope API Error:', errorData);
|
||
|
||
// 尝试解析错误信息
|
||
let errorMessage = `图像扩展API调用失败: ${errorData}`;
|
||
let errorCode = '';
|
||
|
||
try {
|
||
const errorJson = JSON.parse(errorData);
|
||
if (errorJson.code) {
|
||
errorCode = errorJson.code;
|
||
errorMessage = errorJson.message || errorMessage;
|
||
}
|
||
} catch (e) {
|
||
// 解析失败,使用原始错误文本
|
||
}
|
||
|
||
throw new Error(errorMessage);
|
||
}
|
||
|
||
const result = await response.json();
|
||
console.log('DashScope API响应:', result);
|
||
|
||
// 清理临时文件
|
||
try {
|
||
fs.unlinkSync(filePath);
|
||
console.log('临时文件已删除:', filePath);
|
||
} catch (error) {
|
||
console.error('删除临时文件失败:', error);
|
||
}
|
||
|
||
res.json(result);
|
||
} catch (error) {
|
||
console.error('图像扩展错误:', error);
|
||
|
||
// 提供更详细的错误信息
|
||
let statusCode = 500;
|
||
let errorResponse = {
|
||
error: error.message,
|
||
success: false
|
||
};
|
||
|
||
// 如果是文件相关错误
|
||
if (error.code === 'ENOENT') {
|
||
statusCode = 400;
|
||
errorResponse.details = '文件不存在或无法访问';
|
||
} else if (error.message.includes('MinIO')) {
|
||
statusCode = 500;
|
||
errorResponse.details = '文件存储服务错误';
|
||
} else if (error.message.includes('API')) {
|
||
statusCode = 502;
|
||
errorResponse.details = '外部API服务错误';
|
||
}
|
||
|
||
res.status(statusCode).json(errorResponse);
|
||
}
|
||
});
|
||
|
||
// 启动服务器
|
||
startMainServer().catch(err => {
|
||
console.error('启动服务器失败:', err);
|
||
process.exit(1);
|
||
});
|
||
|
||
// 添加图像拓展内存上传路由
|
||
app.post('/api/extend-image-memory', (req, res) => {
|
||
singleUpload(req, res, async (err) => {
|
||
if (err) {
|
||
console.error('文件上传错误:', err);
|
||
return res.status(400).json({ error: '文件上传失败', message: err.message });
|
||
}
|
||
|
||
try {
|
||
console.log('\n=== 开始内存上传图片扩展 ===');
|
||
|
||
// 检查是否有文件上传
|
||
if (!req.file) {
|
||
console.error('没有文件被上传');
|
||
return res.status(400).json({ error: '缺少图片文件' });
|
||
}
|
||
|
||
// 详细记录文件信息
|
||
console.log('上传的文件信息:', {
|
||
fieldname: req.file.fieldname,
|
||
originalname: req.file.originalname,
|
||
encoding: req.file.encoding,
|
||
mimetype: req.file.mimetype,
|
||
size: req.file.size,
|
||
buffer: req.file.buffer ? '存在' : '不存在'
|
||
});
|
||
|
||
// 获取参数
|
||
const mode = req.body.mode || 'outpainting';
|
||
let parameters;
|
||
try {
|
||
parameters = JSON.parse(req.body.parameters || '{}');
|
||
} catch (e) {
|
||
console.error('解析参数错误:', e);
|
||
parameters = {};
|
||
}
|
||
|
||
console.log('扩展模式:', mode);
|
||
console.log('参数:', parameters);
|
||
|
||
// 将buffer写入临时文件
|
||
const tempFilePath = path.join(os.tmpdir(), `temp-${Date.now()}-${req.file.originalname}`);
|
||
fs.writeFileSync(tempFilePath, req.file.buffer);
|
||
console.log('临时文件路径:', tempFilePath);
|
||
|
||
let imageUrl;
|
||
try {
|
||
// 上传到MinIO
|
||
const fileObject = {
|
||
path: tempFilePath,
|
||
originalname: req.file.originalname,
|
||
mimetype: req.file.mimetype
|
||
};
|
||
|
||
imageUrl = await uploadToMinio(fileObject);
|
||
console.log('图片已上传到MinIO:', imageUrl);
|
||
} catch (error) {
|
||
console.error('上传到MinIO失败:', error);
|
||
// 如果上传失败,使用临时文件的base64编码
|
||
const base64Image = req.file.buffer.toString('base64');
|
||
const dataUrl = `data:${req.file.mimetype};base64,${base64Image}`;
|
||
|
||
// 创建一个临时文件并保存到uploads目录
|
||
const uploadsDir = path.join(__dirname, 'uploads');
|
||
if (!fs.existsSync(uploadsDir)) {
|
||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||
}
|
||
|
||
const fileName = `temp-${Date.now()}-${req.file.originalname}`;
|
||
const localFilePath = path.join(uploadsDir, fileName);
|
||
fs.writeFileSync(localFilePath, req.file.buffer);
|
||
|
||
imageUrl = `${req.protocol}://${req.get('host')}/uploads/${fileName}`;
|
||
console.log('使用本地文件URL:', imageUrl);
|
||
}
|
||
|
||
// 构建请求体
|
||
const requestBody = {
|
||
model: "image-out-painting",
|
||
input: {
|
||
image_url: imageUrl
|
||
},
|
||
parameters: parameters
|
||
};
|
||
|
||
// 根据模式设置参数
|
||
if (mode === 'ratio') {
|
||
// 等比例扩图模式
|
||
requestBody.parameters = {
|
||
x_scale: parseFloat(parameters.x_scale) || 1.5,
|
||
y_scale: parseFloat(parameters.y_scale) || 1.5,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false, // 默认为true
|
||
add_watermark: parameters.add_watermark !== false // 默认为true
|
||
};
|
||
} else if (mode === 'direction') {
|
||
// 指定方向扩图模式
|
||
requestBody.parameters = {
|
||
left_offset: parseInt(parameters.left_offset) || 0,
|
||
right_offset: parseInt(parameters.right_offset) || 0,
|
||
top_offset: parseInt(parameters.top_offset) || 0,
|
||
bottom_offset: parseInt(parameters.bottom_offset) || 0,
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else if (mode === 'aspect') {
|
||
// 指定宽高比扩图模式
|
||
requestBody.parameters = {
|
||
aspect_ratio: parameters.aspect_ratio || "16:9",
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
} else {
|
||
// 默认为outpainting模式
|
||
requestBody.parameters = {
|
||
prompt: parameters.prompt || "",
|
||
negative_prompt: parameters.negative_prompt || "",
|
||
best_quality: parameters.best_quality === true,
|
||
limit_image_size: parameters.limit_image_size !== false,
|
||
add_watermark: parameters.add_watermark !== false
|
||
};
|
||
}
|
||
|
||
console.log('发送到DashScope的请求体:', JSON.stringify(requestBody, null, 2));
|
||
|
||
// 调用DashScope API
|
||
const dashscopeResponse = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/image-generation/generation', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${process.env.DASHSCOPE_API_KEY}`
|
||
},
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
const dashscopeData = await dashscopeResponse.json();
|
||
console.log('DashScope API响应:', JSON.stringify(dashscopeData, null, 2));
|
||
|
||
// 清理临时文件
|
||
try {
|
||
fs.unlinkSync(tempFilePath);
|
||
console.log('临时文件已删除');
|
||
} catch (err) {
|
||
console.error('删除临时文件失败:', err);
|
||
}
|
||
|
||
// 返回结果
|
||
if (dashscopeData.output && dashscopeData.output.images && dashscopeData.output.images.length > 0) {
|
||
return res.json({
|
||
success: true,
|
||
result: dashscopeData.output.images[0]
|
||
});
|
||
} else {
|
||
return res.status(500).json({
|
||
success: false,
|
||
error: '图像扩展失败',
|
||
details: dashscopeData
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('处理图像扩展请求时出错:', error);
|
||
return res.status(500).json({
|
||
success: false,
|
||
error: '服务器错误',
|
||
message: error.message
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// 在其他require之后添加下面的代码
|
||
// 导入SDK的token刷新功能
|
||
try {
|
||
const SDK = require('../openapi-sdk-nodejs-20220615/main');
|
||
// 在服务器启动时立即获取一次token
|
||
console.log('正在初始化API Token...');
|
||
SDK.getAppToken()
|
||
.then(data => {
|
||
console.log('成功获取API Token:');
|
||
console.log('- appAuthToken:', data.appAuthToken);
|
||
console.log('- expiresIn:', data.expiresIn, '秒');
|
||
|
||
// 查看当前SDK中的token信息
|
||
const tokenInfo = SDK.getTokenInfo();
|
||
console.log('SDK中的token信息:');
|
||
console.log('- appAuthToken:', tokenInfo.appAuthToken);
|
||
console.log('- requestId:', tokenInfo.requestId);
|
||
console.log('- lastUpdated:', tokenInfo.lastUpdated ? tokenInfo.lastUpdated.toLocaleString() : '未更新');
|
||
|
||
// 检查环境变量是否已更新
|
||
console.log('环境变量token信息:');
|
||
console.log('- APP_AUTH_TOKEN:', process.env.APP_AUTH_TOKEN);
|
||
console.log('- REQUEST_ID:', process.env.REQUEST_ID);
|
||
|
||
// 设置自动刷新
|
||
console.log('初始化API Token自动刷新...');
|
||
// 每1.5小时刷新一次token
|
||
SDK.initTokenRefresh(90 * 60 * 1000);
|
||
console.log('API Token自动刷新已启动');
|
||
})
|
||
.catch(error => {
|
||
console.error('获取API Token失败:', error.message);
|
||
console.log('将在服务器启动后继续尝试获取token...');
|
||
// 尽管初始获取失败,仍然设置自动刷新机制
|
||
SDK.initTokenRefresh(90 * 60 * 1000);
|
||
});
|
||
} catch (error) {
|
||
console.error('初始化API Token自动刷新失败:', error.message);
|
||
}
|
||
|
||
// 服务器启动完成后设置任务计划
|
||
app.on('ready', () => {
|
||
console.log('服务器已就绪,设置定时任务...');
|
||
|
||
// 设置定期清理上传文件的任务 - 每天凌晨3点执行
|
||
const cleanupInterval = process.env.CLEANUP_INTERVAL_HOURS || 24; // 默认24小时
|
||
console.log(`设置文件清理任务,间隔: ${cleanupInterval}小时`);
|
||
|
||
// 立即执行一次清理
|
||
cleanup.runCleanup().catch(err => {
|
||
console.error('初始清理任务失败:', err);
|
||
});
|
||
|
||
// 设置定期清理
|
||
setInterval(() => {
|
||
const now = new Date();
|
||
console.log(`执行定期清理任务: ${now.toISOString()}`);
|
||
cleanup.runCleanup().catch(err => {
|
||
console.error('定期清理任务失败:', err);
|
||
});
|
||
}, cleanupInterval * 60 * 60 * 1000);
|
||
}); |