261 lines
7.6 KiB
TypeScript
261 lines
7.6 KiB
TypeScript
/**
|
||
* 验证已迁移素材是否正常工作
|
||
* 检查URL格式、文件可访问性等
|
||
*/
|
||
|
||
import { PrismaClient } from '@prisma/client';
|
||
|
||
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,
|
||
},
|
||
},
|
||
});
|
||
|
||
// 视频/音频素材类型
|
||
const VIDEO_TYPES = ['主图视频', '安装视频', '种草视频'];
|
||
const AUDIO_TYPES = ['音频素材', '音乐'];
|
||
|
||
/**
|
||
* 检查URL是否可以访问
|
||
*/
|
||
async function checkUrlAccessibility(url: string): Promise<{ accessible: boolean; status?: number; error?: string }> {
|
||
try {
|
||
const response = await fetch(url, {
|
||
method: 'HEAD',
|
||
signal: AbortSignal.timeout(10000), // 10秒超时
|
||
});
|
||
return {
|
||
accessible: response.ok,
|
||
status: response.status,
|
||
};
|
||
} catch (error: any) {
|
||
return {
|
||
accessible: false,
|
||
error: error.message || String(error),
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查URL格式是否正确
|
||
*/
|
||
function isUrlFormatCorrect(url: string): { correct: boolean; issues: string[] } {
|
||
const issues: string[] = [];
|
||
|
||
// 检查是否包含新存储桶域名
|
||
if (!url.includes('s3.yydd.ai')) {
|
||
issues.push('不包含新存储桶域名 s3.yydd.ai');
|
||
}
|
||
|
||
// 检查是否包含bucket名称
|
||
if (!url.includes('gongxiangpan')) {
|
||
issues.push('不包含bucket名称 gongxiangpan');
|
||
}
|
||
|
||
// 检查是否包含路径前缀
|
||
if (!url.includes('由壹点AI/')) {
|
||
issues.push('不包含路径前缀 由壹点AI/');
|
||
}
|
||
|
||
// 检查是否包含错误的jsxiangche
|
||
if (url.includes('jsxiangche')) {
|
||
issues.push('包含错误的jsxiangche路径段');
|
||
}
|
||
|
||
// 检查是否包含旧存储桶域名
|
||
if (url.includes('1080p.hk')) {
|
||
issues.push('包含旧存储桶域名 1080p.hk');
|
||
}
|
||
|
||
return {
|
||
correct: issues.length === 0,
|
||
issues,
|
||
};
|
||
}
|
||
|
||
async function main() {
|
||
console.log('='.repeat(60));
|
||
console.log('验证已迁移素材');
|
||
console.log('='.repeat(60));
|
||
console.log();
|
||
|
||
const args = process.argv.slice(2);
|
||
const checkAccessibility = !args.includes('--no-access-check');
|
||
const sampleSize = args.includes('--sample') ? 10 : undefined;
|
||
|
||
try {
|
||
// 查询所有视频/音频素材
|
||
const allMaterials = await prisma.material.findMany({
|
||
where: {
|
||
materialType: {
|
||
in: [...VIDEO_TYPES, ...AUDIO_TYPES],
|
||
},
|
||
deletedAt: null,
|
||
},
|
||
select: {
|
||
id: true,
|
||
productModel: true,
|
||
materialType: true,
|
||
previewUrl: true,
|
||
videoCoverUrl: true,
|
||
videoPreview540Url: true,
|
||
videoPreviewClipUrl: true,
|
||
} as any,
|
||
});
|
||
|
||
console.log(`查询到 ${allMaterials.length} 个视频/音频素材`);
|
||
console.log();
|
||
|
||
// 筛选出已迁移到新存储桶的素材
|
||
const migratedMaterials: Array<{
|
||
id: string;
|
||
productModel: string;
|
||
materialType: string;
|
||
urls: Array<{ name: string; url: string }>;
|
||
}> = [];
|
||
|
||
for (const material of allMaterials) {
|
||
const m = material as any;
|
||
const urls: Array<{ name: string; url: string }> = [];
|
||
|
||
const urlFields = [
|
||
{ name: 'previewUrl', value: m.previewUrl },
|
||
{ name: 'videoCoverUrl', value: m.videoCoverUrl },
|
||
{ name: 'videoPreview540Url', value: m.videoPreview540Url },
|
||
{ name: 'videoPreviewClipUrl', value: m.videoPreviewClipUrl },
|
||
];
|
||
|
||
for (const field of urlFields) {
|
||
if (field.value && field.value.includes('s3.yydd.ai') && field.value.includes('gongxiangpan')) {
|
||
urls.push({ name: field.name, url: field.value });
|
||
}
|
||
}
|
||
|
||
if (urls.length > 0) {
|
||
migratedMaterials.push({
|
||
id: m.id,
|
||
productModel: m.productModel,
|
||
materialType: m.materialType,
|
||
urls,
|
||
});
|
||
}
|
||
}
|
||
|
||
console.log(`找到 ${migratedMaterials.length} 个已迁移到新存储桶的素材`);
|
||
console.log();
|
||
|
||
if (migratedMaterials.length === 0) {
|
||
console.log('⚠️ 没有找到已迁移的素材');
|
||
return;
|
||
}
|
||
|
||
// 如果指定了采样,只验证部分素材
|
||
const materialsToVerify = sampleSize
|
||
? migratedMaterials.slice(0, sampleSize)
|
||
: migratedMaterials;
|
||
|
||
console.log(`验证 ${materialsToVerify.length} 个素材(${sampleSize ? `采样前${sampleSize}个` : '全部'})`);
|
||
console.log();
|
||
|
||
// 验证结果统计
|
||
let totalUrls = 0;
|
||
let correctFormatUrls = 0;
|
||
let accessibleUrls = 0;
|
||
let inaccessibleUrls = 0;
|
||
const problems: Array<{
|
||
materialId: string;
|
||
productModel: string;
|
||
field: string;
|
||
url: string;
|
||
issue: string;
|
||
}> = [];
|
||
|
||
for (let i = 0; i < materialsToVerify.length; i++) {
|
||
const material = materialsToVerify[i];
|
||
console.log(`[${i + 1}/${materialsToVerify.length}] 素材: ${material.productModel} (${material.materialType})`);
|
||
|
||
for (const urlInfo of material.urls) {
|
||
totalUrls++;
|
||
|
||
// 1. 检查URL格式
|
||
const formatCheck = isUrlFormatCorrect(urlInfo.url);
|
||
if (!formatCheck.correct) {
|
||
problems.push({
|
||
materialId: material.id,
|
||
productModel: material.productModel,
|
||
field: urlInfo.name,
|
||
url: urlInfo.url,
|
||
issue: `URL格式错误: ${formatCheck.issues.join(', ')}`,
|
||
});
|
||
console.log(` ❌ ${urlInfo.name}: URL格式错误`);
|
||
console.log(` 问题: ${formatCheck.issues.join(', ')}`);
|
||
console.log(` URL: ${urlInfo.url}`);
|
||
continue;
|
||
}
|
||
|
||
correctFormatUrls++;
|
||
console.log(` ✅ ${urlInfo.name}: URL格式正确`);
|
||
|
||
// 2. 检查URL可访问性(如果启用)
|
||
if (checkAccessibility) {
|
||
console.log(` 检查可访问性...`);
|
||
const accessCheck = await checkUrlAccessibility(urlInfo.url);
|
||
if (accessCheck.accessible) {
|
||
accessibleUrls++;
|
||
console.log(` ✅ 可访问 (状态码: ${accessCheck.status})`);
|
||
} else {
|
||
inaccessibleUrls++;
|
||
problems.push({
|
||
materialId: material.id,
|
||
productModel: material.productModel,
|
||
field: urlInfo.name,
|
||
url: urlInfo.url,
|
||
issue: `无法访问: ${accessCheck.error || `状态码 ${accessCheck.status}`}`,
|
||
});
|
||
console.log(` ❌ 无法访问: ${accessCheck.error || `状态码 ${accessCheck.status}`}`);
|
||
}
|
||
}
|
||
}
|
||
console.log();
|
||
}
|
||
|
||
// 输出总结
|
||
console.log('='.repeat(60));
|
||
console.log('验证总结');
|
||
console.log('='.repeat(60));
|
||
console.log(`总素材数: ${materialsToVerify.length} 个`);
|
||
console.log(`总URL数: ${totalUrls} 个`);
|
||
console.log(`URL格式正确: ${correctFormatUrls} 个`);
|
||
if (checkAccessibility) {
|
||
console.log(`可访问: ${accessibleUrls} 个`);
|
||
console.log(`无法访问: ${inaccessibleUrls} 个`);
|
||
}
|
||
console.log();
|
||
|
||
if (problems.length > 0) {
|
||
console.log(`⚠️ 发现 ${problems.length} 个问题:`);
|
||
console.log();
|
||
problems.forEach((problem, i) => {
|
||
console.log(`${i + 1}. 素材: ${problem.productModel} (${problem.materialId})`);
|
||
console.log(` 字段: ${problem.field}`);
|
||
console.log(` 问题: ${problem.issue}`);
|
||
console.log(` URL: ${problem.url}`);
|
||
console.log();
|
||
});
|
||
} else {
|
||
console.log('✅ 所有验证通过!已迁移的素材工作正常。');
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('执行失败:', error);
|
||
process.exit(1);
|
||
} finally {
|
||
await prisma.$disconnect();
|
||
}
|
||
}
|
||
|
||
main();
|