gongxiangp2/scripts/check-s3-file.ts

250 lines
7.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.

/**
* 检查S3文件是否存在
* 用于诊断预览图加载失败问题
*
* 使用方法:
* npx tsx scripts/check-s3-file.ts <url>
*
* 示例:
* npx tsx scripts/check-s3-file.ts "http://192.168.58.20:9000/jsxiangche/%E7%94%B1%E5%A3%B9%E7%82%B9AI/video-cover-3e9897ae-8844-446a-a5d6-fb78cf56bc38-1766384242637.jpg"
*/
import { S3Client, HeadObjectCommand, ListBucketsCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
// 从环境变量获取配置
const S3_ENDPOINT = process.env.S3_ENDPOINT || 'http://192.168.58.20:9000';
const S3_BUCKET = process.env.S3_BUCKET || 'gongxiangpan';
const S3_ACCESS_KEY_ID = process.env.S3_ACCESS_KEY_ID || 'GXPAccessKey2025';
const S3_SECRET_ACCESS_KEY = process.env.S3_SECRET_ACCESS_KEY || 'GXPSecretKey2025SecureMinIO';
const S3_REGION = process.env.S3_REGION || 'us-east-1';
const s3Client = new S3Client({
endpoint: S3_ENDPOINT,
region: S3_REGION,
credentials: {
accessKeyId: S3_ACCESS_KEY_ID,
secretAccessKey: S3_SECRET_ACCESS_KEY,
},
forcePathStyle: true, // MinIO requires path-style
});
/**
* 从URL提取bucket名称和key
*/
function parseS3Url(url: string): { bucket: string; key: string } | null {
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(p => p);
if (pathParts.length === 0) {
return null;
}
// MinIO path-style: /bucket-name/key
const bucket = decodeURIComponent(pathParts[0]);
const key = pathParts.slice(1).map(p => decodeURIComponent(p)).join('/');
return { bucket, key };
} catch (error) {
console.error('解析URL失败:', error);
return null;
}
}
/**
* 检查文件是否存在
*/
async function checkFileExists(bucket: string, key: string): Promise<boolean> {
try {
const command = new HeadObjectCommand({
Bucket: bucket,
Key: key,
});
await s3Client.send(command);
return true;
} catch (error: any) {
if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
return false;
}
throw error;
}
}
/**
* 列出所有bucket
*/
async function listBuckets(): Promise<string[]> {
try {
const command = new ListBucketsCommand({});
const response = await s3Client.send(command);
return response.Buckets?.map(b => b.Name || '') || [];
} catch (error) {
console.error('列出bucket失败:', error);
return [];
}
}
/**
* 列出bucket中的对象(限制10个)
*/
async function listObjects(bucket: string, prefix?: string): Promise<string[]> {
try {
const command = new ListObjectsV2Command({
Bucket: bucket,
Prefix: prefix,
MaxKeys: 10,
});
const response = await s3Client.send(command);
return response.Contents?.map(obj => obj.Key || '') || [];
} catch (error) {
console.error('列出对象失败:', error);
return [];
}
}
async function main() {
const url = process.argv[2];
if (!url) {
console.log('使用方法: npx tsx scripts/check-s3-file.ts <url>');
console.log('示例: npx tsx scripts/check-s3-file.ts "http://192.168.58.20:9000/jsxiangche/由壹点AI/video-cover-xxx.jpg"');
process.exit(1);
}
console.log('='.repeat(60));
console.log('S3文件检查工具');
console.log('='.repeat(60));
console.log();
console.log('配置信息:');
console.log(` S3_ENDPOINT: ${S3_ENDPOINT}`);
console.log(` S3_BUCKET: ${S3_BUCKET}`);
console.log(` S3_REGION: ${S3_REGION}`);
console.log();
// 1. 列出所有bucket
console.log('1. 检查可用的bucket...');
const buckets = await listBuckets();
console.log(` 可用bucket: ${buckets.length > 0 ? buckets.join(', ') : '无'}`);
console.log();
// 2. 解析URL
console.log('2. 解析URL...');
console.log(` URL: ${url}`);
const parsed = parseS3Url(url);
if (!parsed) {
console.error(' ❌ URL格式错误,无法解析');
process.exit(1);
}
console.log(` Bucket: ${parsed.bucket}`);
console.log(` Key: ${parsed.key}`);
console.log();
// 3. 检查bucket是否存在
console.log('3. 检查bucket是否存在...');
if (!buckets.includes(parsed.bucket)) {
console.log(` ❌ Bucket "${parsed.bucket}" 不存在`);
console.log(` 可用bucket: ${buckets.join(', ')}`);
console.log();
// 尝试在新bucket中查找(可能有不同的key路径)
if (parsed.bucket === 'jsxiangche' && buckets.includes(S3_BUCKET)) {
console.log(` 提示: 尝试在新bucket "${S3_BUCKET}" 中查找...`);
// 尝试多种可能的key路径
const possibleKeys = [
parsed.key, // 原始key
`由壹点AI/${parsed.key}`, // 添加路径前缀
parsed.key.split('/').slice(1).join('/'), // 移除第一个路径段
`由壹点AI/${parsed.key.split('/').slice(1).join('/')}`, // 移除第一个路径段并添加前缀
];
let found = false;
for (const testKey of possibleKeys) {
const exists = await checkFileExists(S3_BUCKET, testKey);
if (exists) {
console.log(` ✅ 文件存在于新bucket "${S3_BUCKET}" 中`);
console.log(` Key: ${testKey}`);
console.log(` 新URL: ${S3_ENDPOINT}/${S3_BUCKET}/${testKey}`);
found = true;
break;
}
}
if (!found) {
console.log(` ❌ 文件在新bucket "${S3_BUCKET}" 中不存在(尝试了 ${possibleKeys.length} 种key路径)`);
// 列出bucket中的一些文件(用于调试)
console.log(` 列出bucket "${S3_BUCKET}" 中的部分文件...`);
const objects = await listObjects(S3_BUCKET, '由壹点AI/video-cover');
if (objects.length > 0) {
console.log(` 找到 ${objects.length} 个文件(前缀: 由壹点AI/video-cover):`);
objects.slice(0, 5).forEach(obj => {
console.log(` - ${obj}`);
});
} else {
console.log(` 没有找到文件(前缀: 由壹点AI/video-cover)`);
}
}
}
process.exit(1);
}
console.log(` ✅ Bucket "${parsed.bucket}" 存在`);
console.log();
// 4. 检查文件是否存在
console.log('4. 检查文件是否存在...');
try {
const exists = await checkFileExists(parsed.bucket, parsed.key);
if (exists) {
console.log(` ✅ 文件存在`);
console.log();
// 5. 尝试通过HTTP访问
console.log('5. 测试HTTP访问...');
try {
const response = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) });
if (response.ok) {
console.log(` ✅ HTTP访问成功 (${response.status})`);
console.log(` Content-Type: ${response.headers.get('content-type')}`);
console.log(` Content-Length: ${response.headers.get('content-length')}`);
} else {
console.log(` ❌ HTTP访问失败 (${response.status} ${response.statusText})`);
}
} catch (fetchError: any) {
console.log(` ❌ HTTP访问失败: ${fetchError.message}`);
}
} else {
console.log(` ❌ 文件不存在`);
console.log();
// 列出bucket中的一些文件(用于调试)
console.log('6. 列出bucket中的部分文件...');
const objects = await listObjects(parsed.bucket, parsed.key.split('/')[0]);
if (objects.length > 0) {
console.log(` 找到 ${objects.length} 个文件(前缀: ${parsed.key.split('/')[0]}):`);
objects.slice(0, 5).forEach(obj => {
console.log(` - ${obj}`);
});
} else {
console.log(` 没有找到文件(前缀: ${parsed.key.split('/')[0]})`);
}
}
} catch (error: any) {
console.error(` ❌ 检查文件时出错: ${error.message}`);
if (error.$metadata) {
console.error(` HTTP状态码: ${error.$metadata.httpStatusCode}`);
}
}
console.log();
console.log('='.repeat(60));
}
main().catch(error => {
console.error('执行失败:', error);
process.exit(1);
});