/** * 批量上传视频脚本 * 从指定目录批量上传视频文件到服务器 */ const fs = require('fs'); const path = require('path'); const FormData = require('form-data'); const axios = require('axios'); const sqlite3 = require('sqlite3').verbose(); // 配置 const VIDEO_SOURCE_DIR = '/www/wwwroot/ggl/video'; const SERVER_URL = 'http://localhost:3001'; const UPLOAD_ENDPOINT = '/api/videos/upload'; const LOGIN_ENDPOINT = '/api/auth/login'; const DB_PATH = path.join(process.cwd(), 'database', 'goguryeo_video.db'); // 测试用户 const TEST_USER = { username: 'admin', password: 'admin123' }; // 全局token let authToken = null; // 支持的视频格式 const SUPPORTED_FORMATS = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv']; // 默认专题映射(根据文件名关键词) const TOPIC_MAPPING = { '可爱': ['文化艺术', '民俗传统'], '小': ['文化艺术'], '动物': ['民俗传统'], '萌': ['文化艺术'], '治愈': ['文化艺术'], '成长': ['文化艺术', '民俗传统'] }; // 登录获取token async function login() { try { console.log('正在登录获取认证token...'); const response = await axios.post(`${SERVER_URL}${LOGIN_ENDPOINT}`, TEST_USER); if (response.data.success && response.data.data && response.data.data.token) { authToken = response.data.data.token; console.log('✅ 登录成功,获取到token'); return true; } else { console.error('❌ 登录失败:', response.data.message || '未知错误'); return false; } } catch (error) { console.error('❌ 登录请求异常:', error.message); return false; } } // 获取专题ID async function getTopicIds() { return new Promise((resolve, reject) => { const db = new sqlite3.Database(DB_PATH, (err) => { if (err) { reject(err); return; } }); db.all('SELECT id, name FROM topics WHERE status = "active"', (err, rows) => { if (err) { reject(err); } else { const topicMap = {}; rows.forEach(row => { topicMap[row.name] = row.id; }); resolve(topicMap); } db.close(); }); }); } // 根据文件名推断专题 function inferTopics(filename, topicMap) { const topicIds = []; const lowerFilename = filename.toLowerCase(); for (const [keyword, topics] of Object.entries(TOPIC_MAPPING)) { if (lowerFilename.includes(keyword)) { topics.forEach(topicName => { if (topicMap[topicName] && !topicIds.includes(topicMap[topicName])) { topicIds.push(topicMap[topicName]); } }); } } // 如果没有匹配到专题,默认分配到"文化艺术" if (topicIds.length === 0 && topicMap['文化艺术']) { topicIds.push(topicMap['文化艺术']); } return topicIds; } // 生成视频标题和描述 function generateVideoInfo(filename) { // 移除文件扩展名和特殊字符 let title = path.parse(filename).name; title = title.replace(/[\-快手]/g, '').trim(); // 如果标题太长,截取前50个字符 if (title.length > 50) { title = title.substring(0, 50) + '...'; } const description = `精彩视频内容:${title}`; return { title, description }; } // 上传单个视频文件 async function uploadVideo(filePath, topicMap) { const filename = path.basename(filePath); const { title, description } = generateVideoInfo(filename); const topicIds = inferTopics(filename, topicMap); console.log(`正在上传: ${filename}`); console.log(`标题: ${title}`); console.log(`专题: ${topicIds.map(id => Object.keys(topicMap).find(key => topicMap[key] === id)).join(', ')}`); try { const formData = new FormData(); formData.append('video', fs.createReadStream(filePath)); formData.append('title', title); formData.append('description', description); formData.append('category', '文化艺术'); formData.append('topicIds', JSON.stringify(topicIds)); if (!authToken) { throw new Error('未获取到认证token,请先登录'); } const response = await axios.post(`${SERVER_URL}${UPLOAD_ENDPOINT}`, formData, { headers: { ...formData.getHeaders(), 'Authorization': `Bearer ${authToken}` }, maxContentLength: Infinity, maxBodyLength: Infinity, timeout: 300000 // 5分钟超时 }); if (response.data.code === 201 || response.data.code === 200) { console.log(`✅ 上传成功: ${filename}`); return { success: true, filename, videoId: response.data.data.id }; } else { console.error(`❌ 上传失败: ${filename} - ${response.data.message}`); return { success: false, filename, error: response.data.message }; } } catch (error) { console.error(`❌ 上传异常: ${filename} - ${error.message}`); return { success: false, filename, error: error.message }; } } // 获取视频文件列表 function getVideoFiles(directory) { if (!fs.existsSync(directory)) { throw new Error(`目录不存在: ${directory}`); } const files = fs.readdirSync(directory); const videoFiles = files.filter(file => { const ext = path.extname(file).toLowerCase(); return SUPPORTED_FORMATS.includes(ext); }); return videoFiles.map(file => path.join(directory, file)); } // 主函数 async function main() { try { console.log('开始批量上传视频...'); console.log(`源目录: ${VIDEO_SOURCE_DIR}`); // 先登录获取token console.log('\n=== 用户认证 ==='); const loginSuccess = await login(); if (!loginSuccess) { console.log('❌ 登录失败,无法继续上传'); return; } // 获取专题映射 console.log('\n=== 获取专题信息 ==='); const topicMap = await getTopicIds(); console.log('可用专题:', Object.keys(topicMap)); // 获取视频文件列表 console.log('\n=== 扫描视频文件 ==='); const videoFiles = getVideoFiles(VIDEO_SOURCE_DIR); console.log(`发现 ${videoFiles.length} 个视频文件`); if (videoFiles.length === 0) { console.log('没有找到视频文件,退出程序'); return; } // 批量上传 console.log('\n=== 开始上传 ==='); const results = []; for (let i = 0; i < videoFiles.length; i++) { const filePath = videoFiles[i]; console.log(`\n[${i + 1}/${videoFiles.length}]`); const result = await uploadVideo(filePath, topicMap); results.push(result); // 添加延迟避免服务器压力 if (i < videoFiles.length - 1) { console.log('等待 2 秒...'); await new Promise(resolve => setTimeout(resolve, 2000)); } } // 统计结果 console.log('\n=== 上传结果统计 ==='); const successful = results.filter(r => r.success); const failed = results.filter(r => !r.success); console.log(`总计: ${results.length} 个文件`); console.log(`成功: ${successful.length} 个`); console.log(`失败: ${failed.length} 个`); if (failed.length > 0) { console.log('\n失败的文件:'); failed.forEach(f => { console.log(`- ${f.filename}: ${f.error}`); }); } console.log('\n✅ 批量上传完成!'); } catch (error) { console.error('❌ 批量上传过程中出现错误:', error); process.exit(1); } } // 如果直接运行此脚本 if (require.main === module) { main(); } module.exports = { uploadVideo, getVideoFiles, getTopicIds };