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

使用Node.js上传视频至S3播放异常问题求助

问题描述

使用express-fileupload、aws-sdk的s3.upload()上传视频到S3时遇到两个问题:

  • 部分视频返回URL后无法播放
  • 时长超过5分钟的视频播放时经常卡在2分钟以内
    技术栈:Node.js、Express,采用函数式编程范式,上传代码如下:
import aws from "aws-sdk";
const config = require(`${__dirname}/../config/config.js`);
const fs = require('fs');

const bucketName = config.s3.bucket_name;
const region = config.s3.bucket_region;
const accessKeyId =  config.s3.access_key;
const secretAccessKey =  config.s3.secret_key;
import csv from "csvtojson";
import path from "path";
import APP_CONSTANTS from "../constants/app.constant";

aws.config.update({
  region,
  accessKeyId,
  secretAccessKey,
});

const s3 = new aws.S3();


const uploadFile = async (file,type) => {
  let fileName = null;
  let contentType = getFileContentType(file);
  switch(type) {
    case "banner":
      fileName = `banner/${Date.now()}-${file.name}`
      break;
    case "workout":
      fileName = `workout/${Date.now()}-${file.name}`
      break;
    case "calories":
      fileName = `csv/calories/${Date.now()}-${file.name}`
      break;
    case "videos":
      fileName = `videos/${Date.now()}-${file.name}`
      break;
    case "workout-videos":
      fileName = `workout/videos/${Date.now()}-${file.name}`
      break;
    case "daily_tasks":
      fileName = `daily_tasks/${Date.now()}-${file.name}`
      break;
    case "content":
      fileName = `content_of_the_day/${Date.now()}-${file.name}`
      break;
    case "customer":
      fileName = `customer/${Date.now()}-${file.name}`
      break;
    case "content_of_the_day":
      fileName = `content_of_the_day/${Date.now()}-${file.name}`
      break;
    default :
      fileName = `default/${Date.now()}-${file.name}`
      break;
  }

  // Setting up S3 upload parameters
  const params = {
    Bucket: bucketName,
    Key: fileName, // File name you want to save as in S3
    Body: file.data,
    ContentType: contentType
  };
  // Uploading files to the bucket
  const data = await s3.upload(params).promise();
  data.fileName = fileName;
  return data
};
const getFileContentType = (file ) => {
  let contentType = null;
  const extensionName = path.extname(file.name);
  if(APP_CONSTANTS.ALLOWED_IMAGE_EXTENSIONS.includes(extensionName)) {
    contentType = 'image/'+ extensionName.split('.').join('')
  }
  if(APP_CONSTANTS.ALLOWED_VIDEO_EXTENSIONS.includes(extensionName)) {
    contentType = 'video/'+ extensionName.split('.').join('')
  }
  return contentType
}

const s3Bucket = { getImage, uploadFile, getCsv };
export default s3Bucket;
问题根源分析
  1. 视频无法播放:ContentType检测逻辑存在缺陷(大小写不兼容、优先级覆盖),导致S3存储的视频MIME类型错误;部分视频上传不完整,缺少流式播放必需的HTTP头部。
  2. 长视频卡顿:未启用S3分段上传支持,大文件上传易中断;未配置ContentDisposition为inline,导致播放器无法发起Range请求实现流式加载,只能加载部分缓存内容。
解决方案

1. 修复ContentType检测逻辑

优先使用express-fileupload提供的file.mimetype(更准确),同时兼容扩展名检测并统一大小写,避免MIME类型错误。

2. 启用流式播放支持

为视频文件添加ContentDisposition: 'inline'头部,允许播放器发起Range请求;配置分段上传参数,确保大文件上传完整。

3. 增强上传可靠性

添加文件大小校验,捕获上传错误避免返回无效URL;用对象映射替代switch语句提升代码可维护性。

修改后的完整代码
import aws from "aws-sdk";
const config = require(`${__dirname}/../config/config.js`);
import path from "path";
import APP_CONSTANTS from "../constants/app.constant";

aws.config.update({
  region: config.s3.bucket_region,
  accessKeyId: config.s3.access_key,
  secretAccessKey: config.s3.secret_key,
});

const s3 = new aws.S3();

const uploadFile = async (file, type) => {
  // 用对象映射替代switch,提升可维护性
  const typePathMap = {
    "banner": "banner",
    "workout": "workout",
    "calories": "csv/calories",
    "videos": "videos",
    "workout-videos": "workout/videos",
    "daily_tasks": "daily_tasks",
    "content": "content_of_the_day",
    "customer": "customer",
    "content_of_the_day": "content_of_the_day"
  };
  const basePath = typePathMap[type] || "default";
  const fileName = `${basePath}/${Date.now()}-${file.name}`;
  const contentType = getFileContentType(file);

  // 上传参数配置,添加流式播放和完整性校验
  const params = {
    Bucket: config.s3.bucket_name,
    Key: fileName,
    Body: file.data,
    ContentType: contentType,
    ContentLength: file.size, // 校验文件大小,确保上传完整
    ContentDisposition: contentType.startsWith("video/") ? "inline" : "attachment"
  };

  // 配置分段上传:10MB分片,并发4个分片上传,大文件自动启用
  try {
    const data = await s3.upload(params, { partSize: 10 * 1024 * 1024, queueSize: 4 }).promise();
    data.fileName = fileName;
    return data;
  } catch (error) {
    console.error("文件上传失败:", error);
    throw new Error("文件上传失败,请重试");
  }
};

const getFileContentType = (file) => {
  // 优先使用file自带的mimetype,避免扩展名检测错误
  if (file.mimetype) return file.mimetype;
  
  // 扩展名检测:统一转为小写,避免大小写不兼容
  const extensionName = path.extname(file.name).toLowerCase();
  if (APP_CONSTANTS.ALLOWED_IMAGE_EXTENSIONS.includes(extensionName)) {
    return `image/${extensionName.slice(1)}`;
  } else if (APP_CONSTANTS.ALLOWED_VIDEO_EXTENSIONS.includes(extensionName)) {
    return `video/${extensionName.slice(1)}`;
  }
  // 兜底返回二进制流类型
  return "application/octet-stream";
};

const s3Bucket = { getImage, uploadFile, getCsv };
export default s3Bucket;
额外优化建议
  • 配置S3桶的CORS规则,允许前端域名发起视频请求
  • 对长视频转码为HLS/DASH格式(可结合AWS Elemental MediaConvert),进一步优化播放体验
  • 上传后对比本地文件与S3文件的MD5值,确保文件完整性

内容的提问来源于stack exchange,提问作者Humza Faqi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 08:54:55