gongxiangp2/scripts/check-material-files-comprehensive.ts

302 lines
9.9 KiB
TypeScript
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.

/**
* 全面检查素材文件是否存在
* 用于诊断预览图加载失败问题
*
* 检查:
* 1. 旧MinIO服务器(s3.1080p.hk)上是否存在
* 2. 新MinIO服务器(当前MinIO)上是否存在
* 3. 文件是否真的不存在
*
* 使用方法:
* npx tsx scripts/check-material-files-comprehensive.ts <materialId>
*
* 示例:
* npx tsx scripts/check-material-files-comprehensive.ts "3e9897ae-8844-446a-a5d6-fb78cf56bc38"
*/
import { PrismaClient } from '@prisma/client';
import { S3Client, HeadObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
const DATABASE_URL = process.env.DATABASE_URL || 'postgresql://gongxiangpan:gongxiangpan123@192.168.58.20:5432/lobechat';
const prisma = new PrismaClient({
datasources: {
db: {
url: DATABASE_URL,
},
},
});
// 当前MinIO配置
const CURRENT_S3_ENDPOINT = process.env.S3_ENDPOINT || 'http://192.168.58.20:9000';
const CURRENT_S3_BUCKET = process.env.S3_BUCKET || 'gongxiangpan';
const CURRENT_S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || 'GXPAccessKey2025';
const CURRENT_S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY || 'GXPSecretKey2025SecureMinIO';
const CURRENT_S3_REGION = process.env.S3_REGION || 'us-east-1';
// 旧MinIO配置(如果存在)
const OLD_S3_ENDPOINT = 'https://s3.1080p.hk';
const OLD_S3_BUCKET = 'jsxiangche';
const OLD_S3_ACCESS_KEY_ID = process.env.OLD_S3_ACCESS_KEY_ID || '';
const OLD_S3_SECRET_ACCESS_KEY = process.env.OLD_S3_SECRET_ACCESS_KEY || '';
// 创建S3客户端
const currentS3Client = new S3Client({
endpoint: CURRENT_S3_ENDPOINT,
region: CURRENT_S3_REGION,
credentials: {
accessKeyId: CURRENT_S3_ACCESS_KEY_ID,
secretAccessKey: CURRENT_S3_SECRET_ACCESS_KEY,
},
forcePathStyle: true,
});
// 旧S3客户端(如果配置了凭据)
let oldS3Client: S3Client | null = null;
if (OLD_S3_ACCESS_KEY_ID && OLD_S3_SECRET_ACCESS_KEY) {
oldS3Client = new S3Client({
endpoint: OLD_S3_ENDPOINT,
region: 'us-east-1',
credentials: {
accessKeyId: OLD_S3_ACCESS_KEY_ID,
secretAccessKey: OLD_S3_SECRET_ACCESS_KEY,
},
forcePathStyle: true,
});
}
/**
* 从URL提取bucket和key
*/
function parseUrl(url: string): { bucket: string; key: string; domain: string } | null {
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(p => p);
if (pathParts.length === 0) return null;
const bucket = decodeURIComponent(pathParts[0]);
const key = pathParts.slice(1).map(p => decodeURIComponent(p)).join('/');
return { bucket, key, domain: urlObj.hostname };
} catch {
return null;
}
}
/**
* 检查文件是否存在
*/
async function checkFileExists(
client: S3Client,
bucket: string,
key: string,
endpoint: string
): Promise<{ exists: boolean; error?: string }> {
try {
const command = new HeadObjectCommand({
Bucket: bucket,
Key: key,
});
await client.send(command);
return { exists: true };
} catch (error: any) {
if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
return { exists: false };
}
return { exists: false, error: error.message || String(error) };
}
}
/**
* 列出bucket中的对象(用于调试)
*/
async function listObjects(client: S3Client, bucket: string, prefix: string, limit: number = 5): Promise<string[]> {
try {
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: prefix,
MaxKeys: limit,
});
const response = await client.send(command);
return response.Contents?.map(obj => obj.Key || '') || [];
} catch {
return [];
}
}
async function main() {
const materialId = process.argv[2];
if (!materialId) {
console.log('使用方法: npx tsx scripts/check-material-files-comprehensive.ts <materialId>');
console.log('示例: npx tsx scripts/check-material-files-comprehensive.ts "3e9897ae-8844-446a-a5d6-fb78cf56bc38"');
process.exit(1);
}
try {
// 1. 从数据库获取素材信息
console.log('='.repeat(60));
console.log('全面文件检查');
console.log('='.repeat(60));
console.log();
const material = await prisma.material.findUnique({
where: { id: materialId },
select: {
id: true,
productModel: true,
materialType: true,
createdAt: true,
previewUrl: true,
videoCoverUrl: true,
videoPreview540Url: true,
videoPreviewClipUrl: true,
} as any,
});
if (!material) {
console.log(`❌ 素材不存在: ${materialId}`);
process.exit(1);
}
console.log('素材信息:');
console.log(` ID: ${material.id}`);
console.log(` 产品型号: ${material.productModel}`);
console.log(` 素材类型: ${material.materialType}`);
console.log(` 创建时间: ${material.createdAt}`);
console.log();
// 2. 检查各个URL
const urlFields = [
{ name: 'previewUrl', url: (material as any).previewUrl },
{ name: 'videoCoverUrl', url: (material as any).videoCoverUrl },
{ name: 'videoPreview540Url', url: (material as any).videoPreview540Url },
{ name: 'videoPreviewClipUrl', url: (material as any).videoPreviewClipUrl },
];
for (const field of urlFields) {
if (!field.url) {
console.log(`${field.name}: (无)`);
console.log();
continue;
}
console.log(`${field.name}:`);
console.log(` URL: ${field.url}`);
const parsed = parseUrl(field.url);
if (!parsed) {
console.log(` ❌ URL格式错误,无法解析`);
console.log();
continue;
}
console.log(` Bucket: ${parsed.bucket}`);
console.log(` Key: ${parsed.key}`);
console.log(` Domain: ${parsed.domain}`);
console.log();
// 3. 检查旧MinIO服务器(如果URL指向旧服务器)
if (parsed.domain.includes('1080p.hk') || parsed.bucket === 'jsxiangche') {
console.log(` 检查1: 旧MinIO服务器 (${OLD_S3_ENDPOINT})...`);
if (oldS3Client) {
const result = await checkFileExists(oldS3Client, parsed.bucket, parsed.key, OLD_S3_ENDPOINT);
if (result.exists) {
console.log(` ✅ 文件存在于旧MinIO服务器`);
} else {
console.log(` ❌ 文件不存在于旧MinIO服务器`);
if (result.error) {
console.log(` 错误: ${result.error}`);
}
}
} else {
console.log(` ⚠️ 未配置旧MinIO凭据,无法检查`);
console.log(` 提示: 设置环境变量 OLD_S3_ACCESS_KEY_ID 和 OLD_S3_SECRET_ACCESS_KEY`);
}
console.log();
}
// 4. 检查新MinIO服务器(当前MinIO)
console.log(` 检查2: 新MinIO服务器 (${CURRENT_S3_ENDPOINT})...`);
// 尝试原始key
let found = false;
const possibleKeys = [
parsed.key, // 原始key(包含路径前缀)
parsed.key.replace(/^由壹点AI\//, ''), // 移除路径前缀
parsed.key.replace(/^jsxiangche\//, ''), // 移除bucket名称(如果key中包含)
];
// 如果URL使用了旧bucket名称,尝试在新bucket中查找
const testBuckets = parsed.bucket === 'jsxiangche' ? [CURRENT_S3_BUCKET, parsed.bucket] : [parsed.bucket];
for (const testBucket of testBuckets) {
if (found) break;
for (const testKey of possibleKeys) {
if (found) break;
const result = await checkFileExists(currentS3Client, testBucket, testKey, CURRENT_S3_ENDPOINT);
if (result.exists) {
console.log(` ✅ 文件存在于新MinIO服务器`);
console.log(` Bucket: ${testBucket}`);
console.log(` Key: ${testKey}`);
found = true;
break;
}
}
}
if (!found) {
console.log(` ❌ 文件不存在于新MinIO服务器`);
console.log(` 尝试的bucket: ${testBuckets.join(', ')}`);
console.log(` 尝试的key路径: ${possibleKeys.slice(0, 3).join(', ')}`);
// 列出bucket中的一些文件(用于调试)
if (parsed.key.includes('video-cover') || parsed.key.includes('video-thumb')) {
const prefix = '由壹点AI/video-cover';
console.log(` 列出bucket "${CURRENT_S3_BUCKET}" 中的部分文件(前缀: ${prefix})...`);
const objects = await listObjects(currentS3Client, CURRENT_S3_BUCKET, prefix, 5);
if (objects.length > 0) {
console.log(` 找到 ${objects.length} 个文件:`);
objects.forEach(obj => {
console.log(` - ${obj}`);
});
} else {
console.log(` 没有找到文件`);
}
}
}
console.log();
// 5. 总结
console.log(` 总结:`);
if (parsed.domain.includes('1080p.hk') || parsed.bucket === 'jsxiangche') {
if (!found) {
console.log(` ⚠️ URL指向旧MinIO服务器,但文件不在新MinIO服务器中`);
console.log(` 建议:`);
console.log(` 1. 检查文件是否在旧MinIO服务器上`);
console.log(` 2. 如果文件存在,需要迁移到新MinIO服务器`);
console.log(` 3. 如果文件不存在,URL需要更新或文件需要重新上传`);
} else {
console.log(` ✅ 文件在新MinIO服务器中找到(URL需要更新)`);
}
} else {
if (found) {
console.log(` ✅ 文件存在`);
} else {
console.log(` ❌ 文件不存在(可能已被删除)`);
}
}
console.log();
console.log('-'.repeat(60));
console.log();
}
console.log('='.repeat(60));
} catch (error) {
console.error('执行失败:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
main();