You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无法将视频与字幕文件上传至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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 01:45:04