gongxiangp2/scripts/verify-migrated-materials.ts

261 lines
7.6 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.

/**
* 验证已迁移素材是否正常工作
* 检查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();