无法将视频与字幕文件上传至MongoDB的技术求助
问题描述
我用Express+TypeScript后端搭配React前端,想把1个视频文件和1个字幕文件上传到MongoDB。现在代码显示文件上传成功,但数据根本没存进数据库,只有一次偶然成功过。试了几种方法,既没上传成功也没报错,新手求助排查问题并优化代码。
后端代码(server.ts)
import express, { Application, Request, Response } from 'express'; import mongoose from 'mongoose'; import cors from 'cors'; import dotenv from 'dotenv'; const multer = require('multer') const upload = multer({ dest: 'uploads/' }) const { mongoDBURL } = require('./config'); dotenv.config(); const app: Application = express(); // Middleware app.use(cors()); app.use(express.json()); // Database connection const MONGODB_URI = mongoDBURL; mongoose.connect(MONGODB_URI) .then(() => console.log("MongoDB connection established")) .catch(error => console.error("MongoDB connection failed:", error.message)); // Routes app.get('/', (req: Request, res: Response) => { res.send('Hello from Express + TypeScript Server'); }); // Start server const PORT: number | string = process.env.PORT || 5001; app.listen(PORT, () => { console.log(`Server is running on port: ${PORT}`); }); app.post('/upload', upload.fields([{ name: 'video', maxCount: 1 }, { name: 'subtitle', maxCount: 1 }]), (req: Request, res: Response) => { const files = (req as any).files; // Use type assertion here if (files) { res.send({ message: 'Files uploaded successfully.', files: files }); } else { res.status(400).send({ message: 'File upload failed.' }); } });
前端代码(UploadComic组件)
const UploadComic: React.FC = () => { // State to store the uploaded files const [videoFile, setVideoFile] = useState<File | null>(null); const [subtitleFile, setSubtitleFile] = useState<File | null>(null); // Handlers for file selection const handleVideoFileSelect = (file: File | null) => { setVideoFile(file); }; const handleSubtitleFileSelect = (file: File | null) => { setSubtitleFile(file); }; // Inside your component const handleAddToLibrary = () => { if (!videoFile || !subtitleFile) { alert('Missing files'); return; } const formData = new FormData(); formData.append('video', videoFile); // Assuming 'video' is the field name expected by the server formData.append('subtitle', subtitleFile); // Assuming 'subtitle' is the field name expected by the server axios.post('http://localhost:5001/upload', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }) .then((response) => { console.log('Files uploaded successfully1', response.data); // Handle success }) .catch((error) => { console.error('Error uploading files', error); // Handle error }); };
核心问题排查
- 后端无数据库存储逻辑:当前
/upload路由仅用multer把文件存到本地uploads/文件夹,完全没有和MongoDB交互的代码,这是数据没进数据库的根本原因。 - multer配置不合理:默认磁盘存储模式只存文件到本地,没有处理文件内容用于数据库存储;偶然成功大概率是临时加过存储逻辑后又删除,或那次数据库连接刚好异常可用。
- 前端Header设置错误:手动指定
Content-Type: multipart/form-data会丢失边界标识,导致后端无法正确解析FormData。
优化后的完整代码
1. 后端改造(新增模型+完善路由)
第一步:创建MongoDB数据模型(models/VideoSubtitle.ts)
import mongoose from 'mongoose'; const VideoSubtitleSchema = new mongoose.Schema({ video: { data: Buffer, contentType: String, filename: String }, subtitle: { data: Buffer, contentType: String, filename: String }, createdAt: { type: Date, default: Date.now } }); export default mongoose.model('VideoSubtitle', VideoSubtitleSchema);
第二步:修改server.ts,加入数据库存储逻辑
import express, { Application, Request, Response } from 'express'; import mongoose from 'mongoose'; import cors from 'cors'; import dotenv from 'dotenv'; import multer from 'multer'; import VideoSubtitle from './models/VideoSubtitle'; const { mongoDBURL } = require('./config'); dotenv.config(); const app: Application = express(); // Middleware app.use(cors()); app.use(express.json()); // 数据库连接 const MONGODB_URI = mongoDBURL; mongoose.connect(MONGODB_URI) .then(() => console.log("MongoDB连接成功")) .catch(error => console.error("MongoDB连接失败:", error.message)); // Multer配置:内存存储,直接读取文件内容存入数据库 const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); // Routes app.get('/', (req: Request, res: Response) => { res.send('Express + TypeScript 服务器运行中'); }); // 上传接口:新增数据库存储逻辑 app.post('/upload', upload.fields([{ name: 'video', maxCount: 1 }, { name: 'subtitle', maxCount: 1 }]), async (req: Request, res: Response) => { try { const files = (req as any).files; if (!files?.video || !files?.subtitle) { return res.status(400).send({ message: '缺少视频或字幕文件' }); } // 提取文件数据 const videoFile = files.video[0]; const subtitleFile = files.subtitle[0]; // 创建数据库文档 const newEntry = new VideoSubtitle({ video: { data: videoFile.buffer, contentType: videoFile.mimetype, filename: videoFile.originalname }, subtitle: { data: subtitleFile.buffer, contentType: subtitleFile.mimetype, filename: subtitleFile.originalname } }); // 保存到数据库 await newEntry.save(); res.status(201).send({ message: '文件上传并成功存入数据库', id: newEntry._id }); } catch (error) { console.error('上传失败:', error); res.status(500).send({ message: '服务器内部错误', error: (error as Error).message }); } }); // 启动服务器 const PORT: number | string = process.env.PORT || 5001; app.listen(PORT, () => { console.log(`服务器运行在端口: ${PORT}`); });
2. 前端优化(UploadComic组件)
import React, { useState } from 'react'; import axios from 'axios'; const UploadComic: React.FC = () => { const [videoFile, setVideoFile] = useState<File | null>(null); const [subtitleFile, setSubtitleFile] = useState<File | null>(null); const [isLoading, setIsLoading] = useState(false); const handleVideoFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0] || null; setVideoFile(file); }; const handleSubtitleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0] || null; setSubtitleFile(file); }; const handleAddToLibrary = async () => { if (!videoFile || !subtitleFile) { alert('请选择视频和字幕文件'); return; } setIsLoading(true); const formData = new FormData(); formData.append('video', videoFile); formData.append('subtitle', subtitleFile); try { // 无需手动设置Content-Type,axios会自动处理 const response = await axios.post('http://localhost:5001/upload', formData); console.log('上传成功:', response.data); alert('文件上传并存入数据库成功'); // 重置文件选择状态 setVideoFile(null); setSubtitleFile(null); } catch (error) { console.error('上传失败:', error); alert(`上传失败: ${(error as any)?.response?.data?.message || '未知错误'}`); } finally { setIsLoading(false); } }; return ( <div> <div> <label>选择视频文件:</label> <input type="file" accept="video/*" onChange={handleVideoFileSelect} disabled={isLoading} value={videoFile?.name || ''} /> </div> <div> <label>选择字幕文件:</label> <input type="file" accept=".srt,.ass,.vtt" onChange={handleSubtitleFileSelect} disabled={isLoading} value={subtitleFile?.name || ''} /> </div> <button onClick={handleAddToLibrary} disabled={isLoading}> {isLoading ? '上传中...' : '上传到库'} </button> </div> ); }; export default UploadComic;
额外注意事项
- 大文件存储建议:直接把视频存在MongoDB不是最佳实践,大文件推荐用MongoDB GridFS或云存储(如OSS、S3),MongoDB仅存文件URL和元数据。
- 错误捕获:前后端都完善了错误处理逻辑,便于排查问题。
- TypeScript规范:后端替换了
require为import,减少any类型滥用,提升代码可维护性。
内容的提问来源于stack exchange,提问作者Ben Duguid
相关产品推荐
相关产品推荐

