gongxiangp2/yedir/server/utils/minio-client.js
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

204 lines
7.5 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 Minio = require('minio');
const logger = require('./logger');
const dotenv = require('dotenv');
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const fetch = require('node-fetch');
// 重新加载环境变量,确保使用最新配置
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
// 检查是否使用S3存储
const useS3Storage = process.env.USE_S3_STORAGE === 'true';
// 获取MinIO/S3配置
const minioEndpoint = process.env.MINIO_ENDPOINT || 'localhost';
const minioPort = parseInt(process.env.MINIO_PORT || '9000', 10);
const minioUseSSL = process.env.MINIO_USE_SSL === 'true';
const minioAccessKey = process.env.MINIO_ACCESS_KEY || '';
const minioSecretKey = process.env.MINIO_SECRET_KEY || '';
const minioBucketName = process.env.MINIO_BUCKET_NAME || 'images';
const minioRegion = process.env.MINIO_REGION || 'ap-southeast-1';
// 打印MinIO/S3配置
console.log(`${useS3Storage ? 'S3' : 'MinIO'}配置:`, {
endPoint: minioEndpoint,
port: minioPort,
useSSL: minioUseSSL,
accessKey: minioAccessKey ? '已设置' : '未设置',
secretKey: minioSecretKey ? '已设置' : '未设置',
bucketName: minioBucketName,
region: useS3Storage ? minioRegion : undefined
});
// 创建MinIO/S3客户端
// 判断是否使用AWS S3(通过endpoint判断)
const isAwsS3 = minioEndpoint.includes('amazonaws.com');
const shouldSetRegion = useS3Storage || isAwsS3;
// ⚠️ 重要:AWS S3不应该显式指定port,会导致签名错误
// 只有非AWS S3的MinIO才需要port参数
const clientConfig = {
endPoint: minioEndpoint,
useSSL: minioUseSSL,
accessKey: minioAccessKey,
secretKey: minioSecretKey,
region: shouldSetRegion ? minioRegion : undefined // AWS S3必须设置区域
};
// 根据不同存储类型设置额外参数
if (isAwsS3) {
// AWS S3 专用配置
clientConfig.s3ForcePathStyle = false; // 使用虚拟托管风格URL
clientConfig.signatureVersion = 'v4'; // 使用签名版本4
} else {
// MinIO 需要设置port
clientConfig.port = minioPort;
}
console.log('MinIO客户端配置:', {
...clientConfig,
accessKey: clientConfig.accessKey ? '已设置' : '未设置',
secretKey: clientConfig.secretKey ? '已设置' : '未设置'
});
const minioClient = new Minio.Client(clientConfig);
/**
* 测试MinIO/S3连接
* @returns {Promise<boolean>} 连接是否成功
*/
async function checkMinioConnection() {
try {
const bucketExists = await minioClient.bucketExists(minioBucketName);
console.log(`${useS3Storage ? 'S3' : 'MinIO'}存储桶 ${minioBucketName} ${bucketExists ? '存在' : '不存在'}`);
if (!bucketExists) {
console.log(`尝试创建${useS3Storage ? 'S3' : 'MinIO'}存储桶 ${minioBucketName}...`);
if (useS3Storage) {
// S3需要指定区域创建存储桶
await minioClient.makeBucket(minioBucketName, minioRegion);
} else {
await minioClient.makeBucket(minioBucketName);
}
console.log(`${useS3Storage ? 'S3' : 'MinIO'}存储桶 ${minioBucketName} 创建成功`);
}
console.log(`${useS3Storage ? 'S3' : 'MinIO'}连接检查通过`);
return true;
} catch (error) {
console.error(`${useS3Storage ? 'S3' : 'MinIO'}连接检查失败:`, error);
return false;
}
}
/**
* 上传文件到MinIO/S3
* @param {Object} file - 文件对象
* @param {string} [file.path] - 本地文件路径
* @param {Buffer} [file.buffer] - 文件内容缓冲区,当没有path时使用
* @param {string} file.originalname - 原始文件名
* @param {string} file.mimetype - 文件MIME类型
* @param {string} [file.s3Key] - 自定义S3键(路径),如果不提供则自动生成
* @returns {Promise<string>} - 上传成功后的文件URL
*/
async function uploadToMinio(file) {
try {
// 首先测试连接是否正常
const connected = await checkMinioConnection();
if (!connected) {
throw new Error(`${useS3Storage ? 'S3' : 'MinIO'}连接失败,无法上传文件`);
}
// 生成S3键(路径)
const objectName = file.s3Key || `uploads/${Date.now()}-${file.originalname.replace(/\s+/g, '-')}`;
// 准备元数据
const metaData = {
'Content-Type': file.mimetype,
'X-Amz-Meta-Original-Filename': file.originalname
};
// 日志记录
console.log(`上传文件到${useS3Storage ? 'S3' : 'MinIO'}: ${objectName} [${file.mimetype}]`);
// 调试:打印客户端配置(MinIO SDK的实际结构)
console.log('📋 使用的MinIO客户端配置:');
console.log(' 实际客户端对象类型:', typeof minioClient);
console.log(' 是否是全局创建的:', minioClient === module.exports.minioClient);
console.log(' 对象的键:', Object.keys(minioClient).slice(0, 10));
// 尝试打印内部配置(MinIO SDK通常把配置存在私有属性中)
if (minioClient.host) {
console.log(' host:', minioClient.host);
console.log(' port:', minioClient.port);
}
if (minioClient.protocol) {
console.log(' protocol:', minioClient.protocol);
}
if (minioClient.region) {
console.log(' region:', minioClient.region);
}
// 根据文件来源选择上传方法
if (file.buffer) {
// 从Buffer直接上传
await minioClient.putObject(minioBucketName, objectName, file.buffer, file.buffer.length, metaData);
console.log(`从Buffer上传到${objectName}成功`);
} else if (file.path) {
// 从文件路径上传
await minioClient.fPutObject(minioBucketName, objectName, file.path, metaData);
console.log(`从文件${file.path}上传到${objectName}成功`);
} else {
throw new Error('未提供文件路径或内容Buffer');
}
// 生成文件URL
let fileUrl;
// 判断是否是AWS S3(根据endpoint判断)
if (useS3Storage || isAwsS3) {
// 为AWS S3生成正确的访问URL(虚拟托管风格,不带端口号)
fileUrl = `https://${minioBucketName}.s3.${minioRegion}.amazonaws.com/${objectName}`;
console.log(`生成S3访问链接: ${fileUrl}`);
} else {
// 为MinIO生成访问URL
const protocol = minioUseSSL ? 'https' : 'http';
// 如果设置了公网访问的MinIO地址和端口,则使用公网地址
const publicEndpoint = process.env.PUBLIC_MINIO_ENDPOINT || minioEndpoint;
const publicPort = process.env.PUBLIC_MINIO_PORT || minioPort;
// 对于标准端口(80/443),不添加端口号
const portSuffix = (protocol === 'https' && publicPort === 443) || (protocol === 'http' && publicPort === 80)
? ''
: `:${publicPort}`;
fileUrl = `${protocol}://${publicEndpoint}${portSuffix}/${minioBucketName}/${objectName}`;
console.log(`生成MinIO访问链接: ${fileUrl}`);
}
// 测试URL是否可访问(仅当URL以http或https开头时尝试验证)
if (fileUrl.startsWith('http')) {
try {
const testResponse = await fetch(fileUrl, { method: 'HEAD' });
if (testResponse.ok) {
console.log(`URL验证成功: ${fileUrl} 可以正常访问`);
} else {
console.warn(`URL验证警告: ${fileUrl} 无法通过HTTP HEAD请求访问,状态码: ${testResponse.status}`);
}
} catch (testError) {
console.warn(`URL验证警告: ${fileUrl} 无法访问,错误: ${testError.message}`);
}
}
return fileUrl;
} catch (error) {
console.error(`${useS3Storage ? 'S3' : 'MinIO'}上传失败: ${error.message}`);
throw error;
}
}
module.exports = {
minioClient,
uploadToMinio,
checkMinioConnection
};