first commit

This commit is contained in:
2025-09-23 07:35:11 +00:00
commit a5dd3f1335
110 changed files with 46108 additions and 0 deletions

262
scripts/batch-upload-videos.cjs Executable file
View File

@@ -0,0 +1,262 @@
/**
* 批量上传视频脚本
* 从指定目录批量上传视频文件到服务器
*/
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 };