chenpei d0dc94e36d feat: 将弹幕功能替换为AI聊天助手
新功能:
- 创建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助手体验!
2025-10-27 17:45:37 +08:00

4417 lines
170 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 axios = require('axios');
const session = require('express-session');
// 导入路由
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 aiAssistantRoutes = require('./routes/ai-assistant'); // 添加AI助手路由
const notificationsRoutes = require('./routes/notifications'); // 导入通知路由
const settingsRoutes = require('./routes/api/settings'); // 导入设置路由
const proxyRoutes = require('./routes/api/proxy'); // 导入API代理路由
const taobaoProxyRoutes = require('./routes/api/taobao-proxy'); // 导入淘宝API代理路由
const jdProxyRoutes = require('./routes/api/jd-proxy'); // 导入京东API代理路由
const aiAnalyzerRoutes = require('./routes/api/ai-analyzer'); // 导入AI分析器路由
const { sketchToImageRouter, tripo3dRouter, router: indexRouter } = require('./routes/index'); // 导入侧边栏功能设置路由
const ocrRoutes = require('./routes/ocr'); // 导入OCR路由
const eraseRoutes = require('./routes/erase'); // 导入涂抹去除路由
const replaceRoutes = require('./routes/replace'); // 导入涂抹替换路由
const imageGenerator2Routes = require('./routes/image-generator-2'); // 导入图片生成2.0路由
const aiImageEditRoutes = require('./routes/ai-image-edit'); // 导入AI图片编辑2.0路由
const minioS3Routes = require('./routes/s3-config'); // 导入Minio S3配置路由
const cloudStorageRoutes = require('./routes/cloud-storage'); // 导入云存储管理路由
const userCloudRoutes = require('./routes/user-cloud'); // 导入用户云存储路由
const watermarkRemoverRoutes = require('./routes/watermark-remover'); // 导入水印移除路由
const magicAssistantV2Routes = require('./routes/magic-assistant-v2'); // 导入魔法助手2.0路由
const magicAssistantV3Routes = require('./routes/magic-assistant-v3'); // 导入魔法助手3.0路由
const dingtalkRoutes = require('./routes/dingtalk'); // 导入钉钉登录路由
const videoGeneratorRoutes = require('./routes/video-generator'); // 导入视频生成路由
const bgmRoutes = require('./routes/bgm'); // 导入BGM音乐库路由
// 导入清理模块
const cleanup = require('./utils/cleanup');
// 导入定时任务设置模块
const { setupAllCronTasks } = require('./utils/setup-cron');
// 确保.env文件被正确加载
const envPath = path.join(__dirname, '../.env');
console.log('尝试加载.env文件:', envPath);
console.log('.env文件是否存在:', fs.existsSync(envPath));
const envResult = dotenv.config({ path: envPath });
if (envResult.error) {
console.error('加载.env文件失败:', envResult.error);
} else {
console.log('.env文件加载成功');
}
const app = express();
// 添加文件上传大小限制 - 修改为更大的值
app.use(express.json({ limit: '100mb' }));
app.use(express.urlencoded({ limit: '100mb', extended: true }));
app.use(cookieParser());
// 配置会话管理
const sessionSecret = process.env.SESSION_SECRET || 'xiaogongju_session_secret_key';
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: true,
cookie: {
secure: false, // 在本地开发环境禁用secure,允许HTTP连接
maxAge: 7 * 24 * 60 * 60 * 1000, // 7天有效期
sameSite: 'lax',
httpOnly: false // 允许客户端JavaScript访问cookie
},
name: 'xiaogongju.sid' // 自定义session cookie名称
}));
// 启用会话日志记录(用于调试)
app.use((req, res, next) => {
console.log(`[Session] 请求ID: ${req.get('x-request-id') || 'unknown'}, 会话ID: ${req.session.id}, 用户ID: ${req.session.user?._id || '未登录'}`);
next();
});
// 允许所有来源的请求
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() });
});
// 添加配置API获取端点
app.get('/api/config', (req, res) => {
// 只返回特定的API密钥,不暴露其他敏感信息
res.json({
apiKey: process.env.GPT4O_IMAGE_API_KEY,
geminiFlashApiKey: process.env.GEMINI_FLASH_API_KEY,
midjourneyApiKey: process.env.MIDJOURNEY_API_KEY || 'sk-mJ6XuP2QZaW3OO67RkMXeHAZiBUnH7vyrb8LEnYAzkvRcOgo',
midjourneyApiBaseUrl: process.env.MIDJOURNEY_API_BASE_URL || 'https://api.huandutech.com/v1',
// 可以添加其他非敏感配置参数
apiBaseUrl: process.env.GPT4O_API_BASE_URL || 'https://api.huandutech.com/v1'
});
});
// 配置静态文件服务
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')));
app.use('/uploads/videos', express.static(path.join(__dirname, '../uploads/videos'))); // 视频生成器输出目录
app.use('/uploads/temp', express.static(path.join(__dirname, '../uploads/temp'))); // 临时文件目录
app.use('/mindmap', express.static(path.join(__dirname, '../mindmap')));
app.use('/mind-map-main', express.static(path.join(__dirname, '../mind-map-main')));
app.use('/navbar-fragment.html', express.static(path.join(__dirname, '../public/navbar-fragment.html')));
// 添加路径调试信息和额外的静态文件路径
console.log('静态文件路径配置:');
console.log('- 公共路径:', path.join(__dirname, '../public'));
console.log('- 上传路径1:', path.join(__dirname, '../public/uploads'));
console.log('- 上传路径2:', path.join(__dirname, 'uploads/processed'));
console.log('- 视频输出路径:', path.join(__dirname, '../uploads/videos'));
console.log('- 临时文件路径:', path.join(__dirname, '../uploads/temp'));
console.log('- 思维导图路径1:', path.join(__dirname, '../mindmap'));
console.log('- 思维导图路径2:', path.join(__dirname, '../mind-map-main'));
// 确保访问 /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) ||
(process.env.MINIO_S3_ENDPOINT && process.env.MINIO_S3_ACCESS_KEY && process.env.MINIO_S3_SECRET_KEY)) {
// 检查是否使用S3存储
if (process.env.USE_S3_STORAGE === 'true') {
console.log('使用S3存储配置初始化客户端');
// 判断是否是AWS S3
const isAwsS3 = process.env.MINIO_ENDPOINT && process.env.MINIO_ENDPOINT.includes('amazonaws.com');
// 构建客户端配置(AWS S3不设置port以避免签名错误)
const clientConfig = {
endPoint: process.env.MINIO_ENDPOINT,
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY,
region: process.env.MINIO_REGION || 'ap-southeast-1'
};
// 根据不同存储类型设置额外参数
if (isAwsS3) {
// AWS S3 专用配置
clientConfig.s3ForcePathStyle = false; // 使用虚拟托管风格URL
clientConfig.signatureVersion = 'v4'; // 使用签名版本4
} else {
// MinIO 需要设置port
clientConfig.port = parseInt(process.env.MINIO_PORT || '443');
}
minioClient = new Minio.Client(clientConfig);
console.log('S3客户端已配置' + (isAwsS3 ? '(AWS S3,无port参数)' : ''));
} else {
// 原有MinIO配置
console.log('MinIO S3 配置信息:');
console.log('- Endpoint:', process.env.MINIO_S3_ENDPOINT);
console.log('- UseSSL:', process.env.MINIO_S3_USE_SSL);
console.log('- AccessKey:', process.env.MINIO_S3_ACCESS_KEY ? '已设置:' + process.env.MINIO_S3_ACCESS_KEY : '未设置');
console.log('- SecretKey:', process.env.MINIO_S3_SECRET_KEY ? '已设置:' + process.env.MINIO_S3_SECRET_KEY : '未设置');
console.log('- Region:', process.env.MINIO_S3_REGION);
console.log('- Bucket:', process.env.MINIO_S3_BUCKET);
minioClient = new Minio.Client({
endPoint: process.env.MINIO_S3_ENDPOINT,
useSSL: process.env.MINIO_S3_USE_SSL !== 'false',
accessKey: process.env.MINIO_S3_ACCESS_KEY,
secretKey: process.env.MINIO_S3_SECRET_KEY,
region: process.env.MINIO_S3_REGION || 'us-east-1'
});
console.log('MinIO客户端已配置');
}
} else {
console.warn('⚠️ MinIO/S3配置不完整,文件上传功能可能不可用');
// 创建一个空的MinIO客户端,避免代码中的null引用
minioClient = {
bucketExists: async () => {
console.warn('MinIO/S3客户端未配置,bucketExists操作被跳过');
return false;
},
makeBucket: async () => {
console.warn('MinIO/S3客户端未配置,makeBucket操作被跳过');
return false;
},
putObject: async () => {
console.warn('MinIO/S3客户端未配置,putObject操作被跳过');
throw new Error('MinIO/S3客户端未配置');
},
listObjects: () => {
console.warn('MinIO/S3客户端未配置,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 },
{ name: 'image1', maxCount: 1 },
{ name: 'image2', maxCount: 1 },
{ name: 'image3', maxCount: 1 },
{ name: 'image4', maxCount: 1 },
{ name: 'image', maxCount: 1 }
]);
// 添加测试路由 - 确保在其他路由注册之前添加
app.get('/api/test', (req, res) => {
console.log('测试路由被调用');
res.json({ message: '服务器正常运行' });
});
// 将代理图像接口移动到这里
// 添加图像代理端点,用于解决CORS问题
app.get('/api/proxy-image', async (req, res) => {
try {
// 获取URL参数
const imageUrl = req.query.url;
if (!imageUrl) {
console.error('缺少图像URL参数');
return res.status(400).send('Missing image URL parameter');
}
console.log(`代理图像请求,URL: ${imageUrl}`);
// 发送请求获取图像
const response = await axios({
method: 'get',
url: imageUrl,
responseType: 'arraybuffer',
timeout: 10000, // 10秒超时
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br'
}
});
// 设置响应头
const contentType = response.headers['content-type'];
if (contentType) {
res.setHeader('Content-Type', contentType);
} else {
res.setHeader('Content-Type', 'image/jpeg'); // 默认为JPEG
}
// 设置缓存控制
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24小时缓存
// 设置CORS头
res.setHeader('Access-Control-Allow-Origin', '*');
// 返回图像数据
res.send(response.data);
console.log(`图像代理成功: ${imageUrl}`);
} catch (error) {
console.error('图像代理错误:', error.message);
// 返回一个默认的错误图像或404
res.status(500).send(`Error proxying image: ${error.message}`);
}
});
// 添加MinIO测试路由
app.get('/api/test-minio', async (req, res) => {
try {
console.log('测试MinIO/S3连接...');
// 检查配置
if (!process.env.MINIO_BUCKET_NAME || !process.env.MINIO_ENDPOINT) {
throw new Error('存储配置不完整');
}
const isS3 = process.env.USE_S3_STORAGE === 'true';
console.log(`使用${isS3 ? 'S3' : '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, process.env.MINIO_REGION);
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: `${isS3 ? 'S3' : 'MinIO'}连接正常`,
storageType: isS3 ? 'S3' : 'MinIO',
config: {
endpoint: process.env.MINIO_ENDPOINT,
port: process.env.MINIO_PORT,
bucket: process.env.MINIO_BUCKET_NAME,
region: process.env.MINIO_REGION,
useSSL: process.env.MINIO_USE_SSL === 'true'
},
objects: objectList.slice(0, 10) // 只返回前10个对象
});
});
objects.on('error', (err) => {
throw err;
});
} catch (error) {
console.error('存储测试错误:', error);
const isS3 = process.env.USE_S3_STORAGE === 'true';
res.status(500).json({
success: false,
message: `${isS3 ? 'S3' : 'MinIO'}连接测试失败: ${error.message}`,
storageType: isS3 ? 'S3' : 'MinIO',
config: {
endpoint: process.env.MINIO_ENDPOINT,
port: process.env.MINIO_PORT,
bucket: process.env.MINIO_BUCKET_NAME,
region: process.env.MINIO_REGION,
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/user', userCloudRoutes); // 将用户云存储路由移到前面,处理/api/user/*路径
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/replace', replaceRoutes); // 注册涂抹替换路由
app.use('/api/remove-bg', removeBgRoutes);
app.use('/api', imageEnhancerRoutes); // 修改为直接使用/api前缀,这样/enhance-image路由就会变成/api/enhance-image
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路由
app.use('/api/ai-image-edit', aiImageEditRoutes); // 使用AI图片编辑2.0路由
app.use('/api/minio-s3', minioS3Routes); // 注册Minio S3配置路由
app.use('/api/proxy', proxyRoutes); // 注册API代理路由
app.use('/api/taobao', taobaoProxyRoutes); // 注册淘宝API代理路由
app.use('/api/jd', jdProxyRoutes); // 注册京东API代理路由
app.use('/api/ai', aiAnalyzerRoutes); // 注册AI分析器路由
app.use('/api/chunlei-koutu', require('./routes/chunlei-koutu')); // 注册春雷抠图3.0路由
app.use('/api/cloud-storage', cloudStorageRoutes); // 注册云存储管理路由
app.use('/api/watermark-remover', watermarkRemoverRoutes); // 注册水印移除路由
app.use('/api/magic-assistant-v2', magicAssistantV2Routes); // 注册魔法助手2.0路由
app.use('/api/magic-assistant-v3', magicAssistantV3Routes); // 注册魔法助手3.0路由
app.use('/api/magic-replace-v2', require('./routes/magic-replace-v2')); // 注册神奇替换2.0路由
app.use('/api/gemini-flash', require('./routes/gemini-flash')); // 注册Gemini Flash API路由
app.use('/api/dingtalk', dingtalkRoutes); // 注册钉钉登录路由
app.use('/api/video-generator', videoGeneratorRoutes); // 注册视频生成路由
app.use('/api/bgm', bgmRoutes); // 注册BGM音乐库路由
// 注册涂抹去除路由并打印日志
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({
imageData: result.data.image,
userId: userId,
type: 'remove-bg',
originalImageUrl: null, // 这里可以保存原始图片URL,如果有的话
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);
// 构建请求体 - 更新为背景生成2.0 API格式
const requestBody = {
model: process.env.GENERATE_BG_MODEL || "wanx-background-generation-v2",
input: {
base_image_url: baseImageUrl,
...(refImageUrl && { ref_image_url: refImageUrl }),
ref_prompt: combinedPrompt
},
parameters: {
n: parseInt(req.body.output_num) || 1, // 支持生成多张图片
model_version: "v3", // 固定使用V3模型
ref_prompt_weight: parseFloat(req.body.prompt_weight) || 0.5
}
};
// 如果有负向提示词
if (req.body.neg_ref_prompt) {
requestBody.input.neg_ref_prompt = req.body.neg_ref_prompt;
}
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',
'X-DashScope-Timeout': '300' // 增加超时时间到300秒
},
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失败: ${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({
imageData: 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 { limit } = req.query;
// 修改:始终获取所有类型的图片,忽略请求中的类型参数
console.log(`[获取用户图片] 获取所有图片,限制 ${limit || 20} 张`);
const 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
});
}
},
// AI灵魂助手处理函数
async soulAssistantHandler(req, res) {
try {
console.log('---------------------------------------');
console.log('开始处理AI灵魂助手请求...');
console.log('请求头:', req.headers);
console.log('请求体大小:', req.headers['content-length'] || '未知');
console.log('Content-Type:', req.headers['content-type'] || '未知');
// 检查请求
console.log('检查接收到的文件...');
console.log('req.files:', req.files ? Object.keys(req.files) : '无文件');
if (!req.files || (!req.files.image1 && !req.files.image)) {
console.error('错误: 没有上传图片');
throw new Error('请至少上传一张图片');
}
// 打印文件详情
console.log('接收到的文件详情:');
if (req.files.image1) console.log('image1:', req.files.image1[0].originalname, req.files.image1[0].mimetype, `${Math.round(req.files.image1[0].size / 1024)}KB`);
if (req.files.image2) console.log('image2:', req.files.image2[0].originalname, req.files.image2[0].mimetype, `${Math.round(req.files.image2[0].size / 1024)}KB`);
if (req.files.image3) console.log('image3:', req.files.image3[0].originalname, req.files.image3[0].mimetype, `${Math.round(req.files.image3[0].size / 1024)}KB`);
if (req.files.image4) console.log('image4:', req.files.image4[0].originalname, req.files.image4[0].mimetype, `${Math.round(req.files.image4[0].size / 1024)}KB`);
if (req.files.image) console.log('image:', req.files.image[0].originalname, req.files.image[0].mimetype, `${Math.round(req.files.image[0].size / 1024)}KB`);
console.log('接收到的参数:', req.body);
// 保存UI显示名称,但不再使用它作为模型选择
const uiModelName = req.body.model || '模型A图像编辑交互1.0';
// 直接硬编码使用正确的模型名称,无论前端传递什么参数
// 这将在请求中直接使用这个名称
const frontendModelName = 'gemini-2.0-flash-exp-image-generation';
// 提示词保持不变
let prompt = req.body.prompt || '创造性地分析这些图片';
console.log('UI显示的模型名称:', uiModelName);
console.log('实际使用的模型名称:', frontendModelName);
console.log('使用的提示词:', prompt);
// 创建一个数组存储所有图片URL和文件
const imageUrls = [];
const imageFiles = [];
console.log('收集所有上传的图片...');
// 收集所有上传的图片
if (req.files.image) {
// 单图片模式
console.log('单图片模式');
imageFiles.push(req.files.image[0]);
} else {
// 多图片模式
console.log('多图片模式');
for (let i = 1; i <= 4; i++) {
const fieldName = `image${i}`;
if (req.files[fieldName] && req.files[fieldName][0]) {
console.log(`添加图片${i}: ${req.files[fieldName][0].originalname}`);
imageFiles.push(req.files[fieldName][0]);
}
}
}
console.log(`共收集了 ${imageFiles.length} 张图片`);
// 上传原始图片到MinIO以备存档
for (const file of imageFiles) {
const imageUrl = await uploadToMinio(file);
console.log(`原始图片上传至MinIO: ${file.originalname} -> ${imageUrl}`);
imageUrls.push(imageUrl);
}
// 处理图片并调用API
let result = null;
try {
if (imageFiles.length === 1) {
// 单图片处理
console.log('处理单张图片');
// 直接传递实际模型名称而不是前端功能名称
result = await processSingleImage(imageFiles[0], prompt, frontendModelName);
} else {
// 多图片处理
console.log(`处理多张图片: ${imageFiles.length}张`);
// 直接传递实际模型名称而不是前端功能名称
result = await processMultipleImages(imageFiles, prompt, frontendModelName);
}
console.log('API响应成功, 数据结构:', Object.keys(result));
} catch (apiError) {
console.error('API调用过程中出现错误:', apiError.message);
throw apiError; // 继续向上抛出错误
}
// 处理API响应
console.log('提取结果内容...');
let results = [];
let resultImageUrl = null;
// 从API响应中提取图片URL
if (result.choices && result.choices.length > 0) {
const message = result.choices[0].message;
if (message && message.content) {
console.log('找到响应消息内容');
// 尝试从内容中提取图片URL
if (Array.isArray(message.content)) {
console.log('消息内容是数组格式');
// 寻找图片URL
for (const item of message.content) {
if (item.type === 'image_url' && item.image_url && item.image_url.url) {
resultImageUrl = item.image_url.url;
console.log('从数组中找到图片URL:', resultImageUrl);
results.push({
url: resultImageUrl,
prompt: prompt
});
break;
}
}
} else if (typeof message.content === 'string') {
console.log('消息内容是字符串格式');
// 尝试从文本中提取URL
const urlMatch = message.content.match(/(https?:\/\/[^\s]+\.(jpg|jpeg|png|gif))/i);
if (urlMatch) {
resultImageUrl = urlMatch[0];
console.log('从文本中找到图片URL:', resultImageUrl);
results.push({
url: resultImageUrl,
prompt: prompt
});
}
}
}
}
// 如果没有在预期位置找到,尝试在整个响应中搜索
if (!resultImageUrl) {
console.log('在标准位置未找到图片URL,尝试在整个响应中搜索');
// 搜索整个响应JSON
const jsonStr = JSON.stringify(result);
const urlMatches = jsonStr.match(/(https?:\/\/[^\s"]+\.(jpg|jpeg|png|gif))/gi);
if (urlMatches && urlMatches.length > 0) {
resultImageUrl = urlMatches[0].replace(/["\\\r\n]+/g, '');
console.log('在JSON字符串中找到图片URL:', resultImageUrl);
results.push({
url: resultImageUrl,
prompt: prompt
});
}
}
if (!resultImageUrl) {
console.warn('无法从响应中提取图片URL');
}
// 获取用户ID(如果已登录)
console.log('检查用户登录状态...');
let userId = null;
if (req.session && req.session.user && req.session.user._id) {
userId = req.session.user._id;
console.log('从会话中获取到用户ID:', userId);
} else if (req.cookies && req.cookies.token) {
try {
console.log('从cookie中获取token...');
const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
userId = decoded._id;
console.log('从token解析出用户ID:', userId);
} catch (err) {
console.error('解析token失败:', err);
}
}
// 保存生成的图片信息到数据库
if (userId && resultImageUrl) {
try {
console.log('正在保存生成的图片信息到数据库...');
await saveGeneratedImage({
imageData: resultImageUrl,
type: 'soul-assistant',
userId: userId,
prompt: prompt,
metadata: {
sourceImageUrls: imageUrls,
imageCount: imageFiles.length
}
});
console.log('已保存AI灵魂助手生成结果到用户图片库');
} catch (saveError) {
console.error('保存图片信息失败:', saveError);
// 继续处理,不影响主流程
}
} else {
console.log('未获取到用户ID或结果URL,跳过保存到图片库');
}
console.log('准备返回结果到客户端...');
res.json({
success: true,
results: results,
data: {
image_url: resultImageUrl,
prompt: prompt,
source_images: imageUrls,
image_count: imageFiles.length
},
raw_response: result
});
console.log('AI灵魂助手请求处理完成');
console.log('---------------------------------------');
} catch (error) {
console.error('AI灵魂助手错误:', {
message: error.message,
stack: error.stack,
cause: error.cause
});
// 检查错误类型以返回更友好的消息
let errorMessage = error.message || '处理请求时发生未知错误';
let errorDetails = {};
// 检查是否是API错误
if (error.message && error.message.includes('API错误:')) {
errorDetails.apiError = true;
}
// 检查是否是负载饱和错误
if (error.message && error.message.includes('负载已饱和')) {
errorMessage = '服务器负载已饱和,请稍后再试';
errorDetails.isCapacityError = true;
}
// 检查是否有错误响应数据
if (error.response && error.response.data) {
errorDetails.responseData = error.response.data;
// 如果响应中有更具体的错误信息,使用它
if (error.response.data.error) {
errorMessage = error.response.data.error;
}
}
res.status(500).json({
success: false,
error: errorMessage,
details: errorDetails
});
console.log('AI灵魂助手错误处理完成');
console.log('---------------------------------------');
}
}
};
// 定义路由列表
const routes = [
// 示例路由
{ path: '/api/example', method: 'GET', handler: async (req, res) => res.json({ message: 'Hello' }) },
// 常用功能路由
{ path: '/api/ping', method: 'GET', handler: async (req, res) => res.json({ status: 'ok', message: '服务器正常运行中' }) },
// API测试路由
{
path: '/api/test-ai-api', method: 'GET', handler: async (req, res) => {
try {
// 测试Gemini API
const apiUrl = 'https://api.huandutech.com/v1/chat/completions';
const apiKey = process.env.GEMINI_FLASH_API_KEY || 'sk-t6cnVeol01ywWBGXVCtb2DfzbvWJukUCVmgKdvRhvywV6TAd';
console.log('测试AI API连接...');
console.log(`URL: ${apiUrl}`);
console.log(`API Key: ${apiKey.substring(0, 8)}...`);
const response = await axios.post(apiUrl, {
model: "模型A图像编辑交互1.0",
messages: [
{
role: "user",
content: "Hello, can you hear me? This is a test message."
}
]
}, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
});
return res.json({
success: true,
message: 'API测试成功',
status: response.status,
data: response.data
});
} catch (error) {
console.error('API测试失败:', error);
return res.status(500).json({
success: false,
message: 'API测试失败',
error: error.message,
details: error.response ? error.response.data : null
});
}
}
},
// 图像处理路由
{ path: '/api/remove-bg', method: 'POST', handler: routeHandlers.removeBgHandler },
{ path: '/api/remove-bg-v2', method: 'POST', handler: routeHandlers.removeBgV2Handler },
{ 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
});
}
});
}
},
// AI灵魂助手路由
{ path: '/api/soul-assistant', method: 'POST', handler: routeHandlers.soulAssistantHandler }
];
// 注册路由
routes.forEach(route => {
const { path, method, handler } = route;
if (path === '/api/generate-bg' || path === '/api/soul-assistant') {
// 背景生成和AI灵魂助手路由使用多文件上传
app[method.toLowerCase()](path, (req, res, next) => {
// 添加详细日志中间件,特别是对于 soul-assistant 路由
if (path === '/api/soul-assistant') {
console.log(`---------------------------------------`);
console.log(`收到 ${path} 请求, 时间: ${new Date().toISOString()}`);
console.log(`请求头:`, Object.keys(req.headers));
console.log(`请求内容类型: ${req.headers['content-type']}`);
console.log(`开始处理文件上传...`);
}
multiUpload(req, res, (err) => {
if (err) {
if (path === '/api/soul-assistant') {
console.error(`${path} 文件上传出错:`, err);
console.error(`错误类型: ${err.name}, 错误消息: ${err.message}`);
console.error(`错误详情:`, err);
}
next(err);
return;
}
if (path === '/api/soul-assistant') {
console.log(`${path} 文件上传完成`);
console.log(`接收到的文件:`, req.files ? Object.keys(req.files).join(', ') : '无');
console.log(`文件数量:`, req.files ? Object.values(req.files).flat().length : 0);
console.log(`请求参数:`, req.body);
// 拦截响应以记录完成情况
const originalSend = res.send;
res.send = function (data) {
console.log(`${path} 响应完成, 状态码: ${res.statusCode}`);
console.log(`响应大小: ${typeof data === 'string' ? data.length : '未知'} 字节`);
console.log(`---------------------------------------`);
return originalSend.apply(this, arguments);
};
}
console.log(`Received ${path} 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) {
console.log('----------- uploadToMinio 开始 -----------');
console.log(`文件信息: 名称=${file.originalname}, MIME类型=${file.mimetype}, 大小=${file.size} 字节`);
if (process.env.USE_S3_STORAGE === 'true') {
return uploadToS3(file);
}
try {
// 创建 MinIO 客户端(每次上传时创建,避免初始化问题)
const uploadClient = new Minio.Client({
endPoint: process.env.MINIO_S3_ENDPOINT,
useSSL: process.env.MINIO_S3_USE_SSL !== 'false',
accessKey: process.env.MINIO_S3_ACCESS_KEY,
secretKey: process.env.MINIO_S3_SECRET_KEY,
region: process.env.MINIO_S3_REGION || 'us-east-1'
});
// 生成唯一文件名
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: bucket=${process.env.MINIO_S3_BUCKET}, objectName=${objectName}`);
// 检查文件对象类型
let fileData;
let fileSize;
if (file.buffer) {
// 如果是内存中的文件(multer memory storage)
fileData = file.buffer;
fileSize = file.size;
console.log(`使用内存中的文件数据, 大小: ${fileSize} 字节`);
} else if (file.path) {
// 如果是磁盘上的文件(multer disk storage)
console.log(`使用磁盘上的文件, 路径: ${file.path}`);
fileData = fs.createReadStream(file.path);
const stats = fs.statSync(file.path);
fileSize = stats.size;
console.log(`从磁盘读取的文件大小: ${fileSize} 字节`);
} else {
const errorMsg = '无效的文件对象,缺少buffer或path属性';
console.error(errorMsg, file);
throw new Error(errorMsg);
}
// 上传文件
console.log(`开始上传到 MinIO...`);
const startTime = Date.now();
await uploadClient.putObject(
process.env.MINIO_S3_BUCKET,
objectName,
fileData,
fileSize,
{ 'Content-Type': file.mimetype }
);
const uploadTime = Date.now() - startTime;
console.log(`文件上传成功, 耗时: ${uploadTime}ms`);
// 生成访问URL
const fileUrl = `https://${process.env.MINIO_S3_ENDPOINT}/${process.env.MINIO_S3_BUCKET}/${objectName}`;
console.log(`生成访问链接: ${fileUrl}`);
// 跳过验证步骤(因为 statObject 权限不足)
console.log('跳过文件验证(权限限制)');
console.log('----------- uploadToMinio 完成 -----------');
return fileUrl;
} catch (error) {
console.error('----------- uploadToMinio 错误 -----------');
console.error(`上传到 MinIO 失败: ${error.message}`);
console.error(`错误详情:`, error);
// 检查各种常见错误类型
if (error.code === 'AccessDenied') {
console.error('访问被拒绝,可能是凭证问题。检查 accessKey 和 secretKey。');
} else if (error.code === 'NoSuchBucket') {
console.error(`找不到存储桶: ${process.env.MINIO_S3_BUCKET}`);
} else if (error.code === 'ConnectionError' || error.code === 'ECONNREFUSED') {
console.error(`无法连接到 MinIO 服务器: ${process.env.MINIO_S3_ENDPOINT}`);
}
console.error('----------- uploadToMinio 错误结束 -----------');
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 startServer() {
try {
// 连接数据库
await connectDB();
// 设置定时任务
setupAllCronTasks();
// 查找可用端口
const port = await findAvailablePort(3231);
// 启动服务器
const server = app.listen(port, () => {
console.log(`服务器启动成功,端口: ${port}`);
});
// 设置优雅关闭
process.on('SIGTERM', () => {
console.log('收到 SIGTERM 信号,准备关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('收到 SIGINT 信号,准备关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0);
});
});
app.emit('ready');
} catch (err) {
console.error('启动服务器失败:', err);
throw err;
}
}
// 启动服务器
startServer().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);
}
});
// 启动服务器
startServer().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);
}
});
// 启动服务器
startServer().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);
});
// 处理单张图片的辅助函数
async function processSingleImage(imageFile, prompt, model) {
console.log(`处理单张图片 (${imageFile.originalname}) 使用模型: ${model}`);
console.log('注意:忽略输入的模型参数,固定使用gemini-2.0-flash-exp-image-generation');
// 固定使用Gemini API密钥和URL
const apiKey = process.env.GEMINI_FLASH_API_KEY || 'sk-t6cnVeol01ywWBGXVCtb2DfzbvWJukUCVmgKdvRhvywV6TAd';
const apiUrl = 'https://api.huandutech.com/v1';
// 固定使用正确的模型名称
const actualModelName = "gemini-2.0-flash-exp-image-generation";
console.log(`实际使用模型名称: "${actualModelName}"`);
// 获取文件大小(KB)
const fileSizeKB = Math.round(imageFile.size / 1024);
console.log(`图片大小: ${fileSizeKB}KB`);
// 获取文件的base64编码
const base64Image = imageFile.buffer.toString('base64');
console.log(`图片base64长度: ${base64Image.length}`);
// 使用OpenAI标准格式构建请求体
const requestBody = {
model: actualModelName,
messages: [
{
role: "user",
content: [
{
type: "text",
text: prompt
},
{
type: "image_url",
image_url: {
// 使用OpenAI标准的base64格式
url: `data:${imageFile.mimetype};base64,${base64Image}`
}
}
]
}
],
max_tokens: 300
};
// 记录请求体大小
const requestBodySize = JSON.stringify(requestBody).length;
console.log(`请求体大小: ${Math.round(requestBodySize / 1024)}KB`);
console.log(`请求体model字段: "${requestBody.model}"`);
console.log(`API URL: ${apiUrl}/chat/completions`);
console.log('API Key前8位:', apiKey.substring(0, 8));
console.log('使用标准OpenAI格式发送请求');
try {
const response = await axios.post(`${apiUrl}/chat/completions`, requestBody, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
// 增加请求超时时间到300秒(5分钟),为复杂图像处理提供足够时间
timeout: 300000
});
console.log('API调用成功,状态码:', response.status);
return response.data;
} catch (error) {
console.error('API调用失败:', error.message);
// 详细记录错误信息
if (error.response) {
console.error('响应状态:', error.response.status);
console.error('响应数据:', JSON.stringify(error.response.data).substring(0, 500));
// 基于HTTP状态码处理常见错误
if (error.response.status === 504) {
throw new Error('服务器响应超时,请稍后再试');
} else if (error.response.status === 429) {
throw new Error('请求频率过高,请稍后再试');
} else if (error.response.status >= 500) {
throw new Error('服务器内部错误,请稍后再试');
}
// 如果有响应数据,尝试提取错误信息
if (error.response.data) {
let errorMessage = '';
// 处理error.error是对象的情况
if (typeof error.response.data.error === 'object' && error.response.data.error !== null) {
errorMessage = error.response.data.error.message || JSON.stringify(error.response.data.error);
}
// 处理error.error是字符串的情况
else if (typeof error.response.data.error === 'string') {
errorMessage = error.response.data.error;
}
// 其他情况
else {
errorMessage = JSON.stringify(error.response.data);
}
if (errorMessage) {
throw new Error(`API错误: ${errorMessage}`);
}
}
}
// 默认错误
throw new Error(`API调用失败: ${error.message}`);
}
}
// --- Process Multiple Images for Soul Assistant ---
// Modify this function to accept the model parameter and select the API key
async function processMultipleImages(imageFiles, prompt, model) {
console.log(`处理多张图片 (${imageFiles.length}张) 使用模型: ${model}`);
console.log('注意:忽略输入的模型参数,固定使用gemini-2.0-flash-exp-image-generation');
// 固定使用Gemini API密钥和URL
const apiKey = process.env.GEMINI_FLASH_API_KEY || 'sk-t6cnVeol01ywWBGXVCtb2DfzbvWJukUCVmgKdvRhvywV6TAd';
const apiUrl = 'https://api.huandutech.com/v1';
// 记录所有图片的大小
let totalSizeKB = 0;
imageFiles.forEach(file => {
const fileSizeKB = Math.round(file.size / 1024);
totalSizeKB += fileSizeKB;
console.log(`图片 ${file.originalname} 大小: ${fileSizeKB}KB`);
});
console.log(`总图片大小: ${totalSizeKB}KB`);
// 转换图片为base64格式,使用OpenAI标准格式
const imageContent = imageFiles.map(file => {
const base64Image = file.buffer.toString('base64');
console.log(`图片 ${file.originalname} base64长度: ${base64Image.length}`);
// 使用OpenAI标准格式:image_url对象
return {
type: "image_url",
image_url: {
url: `data:${file.mimetype};base64,${base64Image}`
}
};
});
// 固定使用正确的模型名称
const actualModelName = "gemini-2.0-flash-exp-image-generation";
console.log(`实际使用模型名称: "${actualModelName}"`);
// 构建OpenAI标准格式的请求体
const requestBody = {
model: actualModelName,
messages: [
{
role: "user",
content: [
// 首先添加文本提示
{
type: "text",
text: prompt
},
// 然后添加所有图片
...imageContent
]
}
],
max_tokens: 300
};
// 记录请求体大小
const requestBodySize = JSON.stringify(requestBody).length;
console.log(`请求体大小: ${Math.round(requestBodySize / 1024)}KB`);
console.log(`请求体model字段: "${requestBody.model}"`);
console.log(`API URL: ${apiUrl}/chat/completions`);
console.log('API Key前8位:', apiKey.substring(0, 8));
console.log('使用标准OpenAI格式发送请求');
try {
const response = await axios.post(`${apiUrl}/chat/completions`, requestBody, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
// 增加请求超时时间到300秒(5分钟),为复杂图像处理提供足够时间
timeout: 300000
});
console.log('API调用成功,状态码:', response.status);
return response.data;
} catch (error) {
console.error('API调用失败:', error.message);
// 详细记录错误信息
if (error.response) {
console.error('响应状态:', error.response.status);
console.error('响应数据:', JSON.stringify(error.response.data).substring(0, 500));
// 基于HTTP状态码处理常见错误
if (error.response.status === 504) {
throw new Error('服务器响应超时,请稍后再试');
} else if (error.response.status === 429) {
throw new Error('请求频率过高,请稍后再试');
} else if (error.response.status >= 500) {
throw new Error('服务器内部错误,请稍后再试');
}
// 如果有响应数据,尝试提取错误信息
if (error.response.data) {
let errorMessage = '';
// 处理error.error是对象的情况
if (typeof error.response.data.error === 'object' && error.response.data.error !== null) {
errorMessage = error.response.data.error.message || JSON.stringify(error.response.data.error);
}
// 处理error.error是字符串的情况
else if (typeof error.response.data.error === 'string') {
errorMessage = error.response.data.error;
}
// 其他情况
else {
errorMessage = JSON.stringify(error.response.data);
}
if (errorMessage) {
throw new Error(`API错误: ${errorMessage}`);
}
}
}
// 默认错误
throw new Error(`API调用失败: ${error.message}`);
}
}
// 显示关键环境变量
console.log('==== 关键API配置 ====');
console.log(`GEMINI_FLASH_API_KEY: ${process.env.GEMINI_FLASH_API_KEY ? '已设置' : '未设置'}`);
console.log(`GPT4O_IMAGE_API_KEY: ${process.env.GPT4O_IMAGE_API_KEY ? '已设置' : '未设置'}`);
console.log('====================');
// MinIO配置信息
console.log('MinIO配置:', {
endPoint: process.env.MINIO_ENDPOINT,
port: process.env.MINIO_PORT,
useSSL: process.env.MINIO_USE_SSL,
bucketName: process.env.MINIO_BUCKET_NAME
});
// 在uploadToMinio函数前添加S3上传函数
async function uploadToS3(file) {
console.log('----------- uploadToS3 开始 -----------');
console.log(`文件信息: 名称=${file.originalname}, MIME类型=${file.mimetype}, 大小=${file.size} 字节`);
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(`准备上传文件到 S3: bucket=${process.env.MINIO_BUCKET_NAME}, objectName=${objectName}, region=${process.env.MINIO_REGION}`);
// 检查文件对象类型
let fileData;
let fileSize;
if (file.buffer) {
// 如果是内存中的文件(multer memory storage)
fileData = file.buffer;
fileSize = file.size;
console.log(`使用内存中的文件数据, 大小: ${fileSize} 字节`);
} else if (file.path) {
// 如果是磁盘上的文件(multer disk storage)
console.log(`使用磁盘上的文件, 路径: ${file.path}`);
fileData = fs.createReadStream(file.path);
const stats = fs.statSync(file.path);
fileSize = stats.size;
console.log(`从磁盘读取的文件大小: ${fileSize} 字节`);
} else {
const errorMsg = '无效的文件对象,缺少buffer或path属性';
console.error(errorMsg, file);
throw new Error(errorMsg);
}
// 上传文件
console.log(`开始上传到 S3...`);
const startTime = Date.now();
await minioClient.putObject(
process.env.MINIO_BUCKET_NAME,
objectName,
fileData,
fileSize,
{ 'Content-Type': file.mimetype }
);
const uploadTime = Date.now() - startTime;
console.log(`文件上传成功, 耗时: ${uploadTime}ms`);
// 为S3生成正确的访问URL
const fileUrl = `https://${process.env.MINIO_BUCKET_NAME}.s3.${process.env.MINIO_REGION}.amazonaws.com/${objectName}`;
console.log(`生成S3访问链接: ${fileUrl}`);
console.log('----------- uploadToS3 完成 -----------');
return fileUrl;
} catch (error) {
console.error('----------- uploadToS3 错误 -----------');
console.error(`上传到 S3 失败: ${error.message}`);
console.error(`错误详情:`, error);
if (error.code === 'AccessDenied') {
console.error('访问被拒绝,可能是凭证问题。检查 accessKey 和 secretKey。');
} else if (error.code === 'NoSuchBucket') {
console.error(`找不到存储桶: ${process.env.MINIO_BUCKET_NAME}`);
}
console.error('----------- uploadToS3 错误结束 -----------');
throw new Error(`上传到 S3 失败: ${error.message}`);
}
}
// 添加图像代理端点,用于解决CORS问题
app.get('/api/proxy-image', async (req, res) => {
try {
// 获取URL参数
const imageUrl = req.query.url;
if (!imageUrl) {
console.error('缺少图像URL参数');
return res.status(400).send('Missing image URL parameter');
}
console.log(`代理图像请求,URL: ${imageUrl}`);
// 发送请求获取图像
const response = await axios({
method: 'get',
url: imageUrl,
responseType: 'arraybuffer',
timeout: 10000, // 10秒超时
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br'
}
});
// 设置响应头
const contentType = response.headers['content-type'];
if (contentType) {
res.setHeader('Content-Type', contentType);
} else {
res.setHeader('Content-Type', 'image/jpeg'); // 默认为JPEG
}
// 设置缓存控制
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24小时缓存
// 设置CORS头
res.setHeader('Access-Control-Allow-Origin', '*');
// 返回图像数据
res.send(response.data);
console.log(`图像代理成功: ${imageUrl}`);
} catch (error) {
console.error('图像代理错误:', error.message);
// 返回一个默认的错误图像或404
res.status(500).send(`Error proxying image: ${error.message}`);
}
});
// 启用会话
app.use(session({
secret: process.env.SESSION_SECRET || 'default_session_secret',
resave: false,
saveUninitialized: true,
cookie: { secure: process.env.NODE_ENV === 'production' }
}));
// 添加API图片结果保存中间件
const saveApiImageResult = require('./middlewares/image-saver');
app.use('/api/', saveApiImageResult);