杰瑞科技汇

Java视频上传教程,如何实现?

  1. 基础篇:使用 Servlet 和 Apache Commons FileUpload 实现简单上传

    Java视频上传教程,如何实现?-图1
    (图片来源网络,侵删)
    • 原理介绍
    • 环境搭建
    • 前端代码
    • 后端代码
    • 测试与问题
  2. 进阶篇:使用 Spring Boot 实现上传

    • 为什么选择 Spring Boot
    • 项目搭建与配置
    • 控制器代码
    • 高级配置(文件大小、类型限制)
  3. 生产环境最佳实践

    • 文件存储:为什么不能存在服务器本地?(使用云存储OSS)
    • 文件处理:视频封面、转码(使用 FFmpeg)
    • 安全性:防止恶意文件上传
    • 用户体验:上传进度条

基础篇:使用 Servlet 和 Apache Commons FileUpload

这是最传统、最核心的 Java Web 上传方式,理解它有助于你掌握上传的本质。

原理介绍

标准的 HTTP 请求在传输文件时,会使用 multipart/form-data 内容类型,浏览器会将表单中的每个字段(包括文件)拆分成多个部分(Part)进行发送,Java Servlet 本身不直接处理这种复杂的请求格式,因此我们需要借助第三方库,Apache Commons FileUpload,它能够解析这个请求,帮我们轻松地获取到上传的文件。

Java视频上传教程,如何实现?-图2
(图片来源网络,侵删)

环境搭建

  1. 创建项目:使用任何你喜欢的 IDE(如 IntelliJ IDEA, Eclipse)创建一个 Dynamic Web Project。

  2. 添加依赖:你需要两个核心库:

    • commons-fileupload:用于解析 multipart 请求。
    • commons-io:提供一些实用的 I/O 工具类,简化代码。

    如果你使用 Maven,在 pom.xml 中添加以下依赖:

    <dependencies>
        <!-- Servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- Apache Commons FileUpload -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
        <!-- Apache Commons IO -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>

前端代码 (upload.html)

创建一个简单的 HTML 页面,用于选择和上传文件,注意 form 的两个关键属性:

Java视频上传教程,如何实现?-图3
(图片来源网络,侵删)
  • enctype="multipart/form-data":必须设置,表示表单包含文件。
  • method="POST":上传必须使用 POST 方法。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">视频上传</title>
</head>
<body>
    <h1>上传视频教程</h1>
    <form action="upload" method="post" enctype="multipart/form-data">
        <label for="videoFile">选择视频文件:</label>
        <input type="file" id="videoFile" name="videoFile" accept="video/*" required>
        <button type="submit">上传</button>
    </form>
</body>
</html>

后端代码 (UploadServlet.java)

创建一个 Servlet 来处理上传请求。

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    // 上传文件的目录
    private static final String UPLOAD_DIR = "uploads";
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 检查是否是 multipart 请求
        if (!ServletFileUpload.isMultipartContent(request)) {
            // 如果不是,则停止处理
            PrintWriter writer = response.getWriter();
            writer.println("Error: 表单必须包含 enctype=multipart/form-data");
            writer.flush();
            return;
        }
        // 配置上传参数
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 设置内存临界值 - 超过后将产生临时文件存储于临时目录
        factory.setSizeThreshold(1024 * 1024 * 3); // 3MB
        // 设置临时存储目录
        File tempDir = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
        factory.setRepository(tempDir);
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 设置最大文件大小 (10MB)
        upload.setSizeMax(1024 * 1024 * 10);
        String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIR;
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }
        try {
            // 解析请求内容,提取文件
            List<FileItem> formItems = upload.parseRequest(request);
            if (formItems != null && formItems.size() > 0) {
                for (FileItem item : formItems) {
                    // 处理不在表单中的字段(即文件)
                    if (!item.isFormField()) {
                        String fileName = new File(item.getName()).getName();
                        String filePath = uploadPath + File.separator + fileName;
                        File storeFile = new File(filePath);
                        // 在控制台打印上传文件的信息
                        System.out.println("文件上传到了: " + filePath);
                        // 保存文件到硬盘
                        item.write(storeFile);
                    }
                }
            }
            // 上传成功,重定向到成功页面
            getServletContext().getRequestDispatcher("/success.html").forward(request, response);
        } catch (Exception ex) {
            throw new ServletException("上传文件失败", ex);
        }
    }
}

测试与问题

  1. 部署:将项目部署到 Tomcat 等服务器上。
  2. 访问:在浏览器中访问 http://localhost:8080/你的项目名/upload.html
  3. 上传:选择一个视频文件并点击上传。
  4. 检查:上传成功后,你会在项目的 webapp 目录下看到一个名为 uploads 的文件夹,里面有你上传的视频文件。

常见问题

  • java.io.tmpdir 空间不足:如果上传的文件很大,可能会先存放在临时目录,确保服务器的临时目录有足够空间。
  • 文件名乱码FileItem.getName() 在不同浏览器下可能返回不同的编码(如 ISO-8859-1),如果文件名是中文,需要转换编码:new String(item.getName().getBytes("ISO-8859-1"), "UTF-8")
  • 文件大小限制:如果文件超过 upload.setSizeMax() 设置的大小,会抛出 SizeLimitExceededException

进阶篇:使用 Spring Boot 实现上传

Spring Boot 大大简化了文件上传的开发,通过自动配置,我们只需要几行代码就能完成。

为什么选择 Spring Boot?

  • 简化配置:无需手动配置 ServletFileUpload 组件。
  • 嵌入式服务器:无需额外安装 Tomcat。
  • 强大的生态系统:与 Spring MVC 无缝集成,易于扩展。

项目搭建与配置

  1. 使用 Spring Initializr 创建一个新项目。

    • Project: Maven Project
    • Language: Java
    • Spring Boot: 选择一个稳定版本 (e.g., 3.x.x)
    • Project Metadata: Group, Artifact, Name 等
    • Dependencies: 添加 Spring Web
  2. 配置文件 (application.properties)

    src/main/resources/application.properties 中添加配置,限制上传文件的大小。

    # 单个文件最大大小 (10MB)
    spring.servlet.multipart.max-file-size=10MB
    # 总请求最大大小 (包含多个文件) (11MB)
    spring.servlet.multipart.max-request-size=11MB
    # 临时文件存储路径 (可选,Spring Boot会自动处理)
    # spring.servlet.multipart.location=/tmp

控制器代码 (FileUploadController.java)

创建一个控制器来处理文件上传请求。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class FileUploadController {
    // 定义上传文件的目录
    private static final String UPLOADED_FOLDER = "uploads/";
    @PostMapping("/upload")
    @ResponseBody
    public String handleFileUpload(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "请选择一个文件上传";
        }
        try {
            // 创建上传目录
            Path uploadPath = Paths.get(UPLOADED_FOLDER);
            if (!Files.exists(uploadPath)) {
                Files.createDirectories(uploadPath);
            }
            // 获取原始文件名
            String originalFilename = file.getOriginalFilename();
            // 构建目标文件路径
            Path destinationFile = uploadPath.resolve(originalFilename);
            // 将文件保存到目标路径
            Files.copy(file.getInputStream(), destinationFile);
            return "文件上传成功: " + destinationFile.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return "文件上传失败: " + e.getMessage();
        }
    }
}

高级配置(文件类型限制)

在实际应用中,你可能只允许特定类型的文件上传,可以通过 MultipartFilegetContentType() 或文件后缀名来检查。

@PostMapping("/upload-video")
@ResponseBody
public String handleVideoUpload(@RequestParam("file") MultipartFile file) {
    // 检查文件是否为空
    if (file.isEmpty()) {
        return "请选择一个文件上传";
    }
    // 检查文件类型
    String contentType = file.getContentType();
    if (contentType == null || !contentType.startsWith("video/")) {
        return "只允许上传视频文件!";
    }
    // ... (后续的保存逻辑)
    return "视频文件上传成功!";
}

生产环境最佳实践

上面的基础和进阶教程实现了基本功能,但在生产环境中远远不够。

文件存储:为什么不能存在服务器本地?

将文件直接存在服务器本地(webapp/uploads)是极其危险的,原因如下:

  • 数据丢失:如果服务器宕机、重装系统或 Docker 容器被删除,所有上传的文件都会丢失。
  • 扩展性差:当需要增加服务器进行负载均衡时,文件无法在多台服务器之间共享。
  • 安全性低:文件暴露在 Web 根目录下,可能被直接通过 URL 访问,存在安全隐患。
  • 成本高:服务器磁盘空间有限且昂贵。

解决方案:使用云存储服务

主流云服务商都提供了对象存储服务,这是处理文件的最佳选择。

  • 阿里云 OSS
  • 腾讯云 COS
  • Amazon S3
  • MinIO (自建 S3 兼容的私有云存储)

如何集成(以阿里云 OSS 为例)

  1. 添加依赖

    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.15.1</version>
    </dependency>
  2. 配置信息:在 application.properties 中配置你的 OSS 信息。

    # 阿里云 OSS
    aliyun.oss.accessKeyId=你的AccessKeyId
    aliyun.oss.accessKeySecret=你的AccessKeySecret
    aliyun.oss.bucketName=你的BucketName
    aliyun.oss.endpoint=你的Endpoint地址
  3. 编写上传服务

    @Service
    public class OssService {
        @Value("${aliyun.oss.accessKeyId}")
        private String accessKeyId;
        @Value("${aliyun.oss.accessKeySecret}")
        private String accessKeySecret;
        @Value("${aliyun.oss.bucketName}")
        private String bucketName;
        @Value("${aliyun.oss.endpoint}")
        private String endpoint;
        public String uploadFile(MultipartFile file) throws IOException {
            // 创建OSSClient实例
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            String originalFilename = file.getOriginalFilename();
            // 上传文件流
            InputStream inputStream = file.getInputStream();
            ossClient.putObject(bucketName, originalFilename, inputStream);
            // 关闭OSSClient
            ossClient.shutdown();
            // 返回文件的URL
            return "https://" + bucketName + "." + endpoint + "/" + originalFilename;
        }
    }
  4. 在 Controller 中调用服务

    @PostMapping("/upload-to-oss")
    @ResponseBody
    public String uploadToOss(@RequestParam("file") MultipartFile file) {
        try {
            String fileUrl = ossService.uploadFile(file);
            return "文件上传成功,访问地址: " + fileUrl;
        } catch (IOException e) {
            return "上传失败: " + e.getMessage();
        }
    }

文件处理:视频封面、转码

上传后的视频通常不能直接播放,需要处理。

  • 视频封面:使用 FFmpeg 截取视频的第一帧作为封面。

    • 在服务器上安装 FFmpeg。
    • 通过 Java 调用 FFmpeg 的命令行工具来执行截图。
    // 示例:使用 ProcessBuilder 调用 FFmpeg
    String videoPath = "path/to/your/video.mp4";
    String coverPath = "path/to/your/cover.jpg";
    ProcessBuilder pb = new ProcessBuilder("ffmpeg", "-i", videoPath, "-ss", "00:00:01", "-vframes", "1", coverPath);
    Process process = pb.start();
    process.waitFor();
  • 视频转码:将视频转码为更通用的格式(如 MP4/H.264)和不同的分辨率(如 720p, 1080p),以适应不同设备和网络条件,同样使用 FFmpeg 实现。

安全性:防止恶意文件上传

这是最关键的一环,绝不能忽视。

  • 验证文件类型

    • 不要只信任 Content-Type:因为 Content-Type 可以被轻易伪造。
    • 检查文件后缀名:白名单机制,只允许 .mp4, .mov, .avi 等特定后缀。
    • 检查文件“魔术数字”(Magic Number):读取文件的前几个字节,判断其真实的文件类型,MP4 文件的开头通常是 ftyp,这是最可靠的方法。
  • 重命名文件:不要使用用户上传的原始文件名,可以生成一个唯一的、随机的文件名(如 UUID),防止文件名冲突和恶意脚本(如 ../../../shell.php)。

  • 设置文件权限:确保上传的文件不具有可执行权限。

  • 病毒扫描:对于重要的业务,可以集成杀毒软件 API 对上传文件进行扫描。

用户体验:上传进度条

大文件上传需要很长时间,进度条能极大提升用户体验。

  • 前端实现:使用 HTML5 的 XMLHttpRequest 或 Fetch API 的 onUploadProgress 事件。
  • 后端实现:后端不需要做特殊处理,但需要确保服务器配置(如 max-file-size)允许大文件上传,并且有足够的超时时间。

前端示例 (Fetch API):

<input type="file" id="myFile">
<button id="uploadBtn">上传</button>
<progress id="progressBar" value="0" max="100"></progress>
<script>
document.getElementById('uploadBtn').addEventListener('click', function() {
    const file = document.getElementById('myFile').files[0];
    const formData = new FormData();
    formData.append('file', file);
    fetch('/upload-to-oss', {
        method: 'POST',
        body: formData,
        onUploadProgress: function(progressEvent) {
            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
            document.getElementById('progressBar').value = percentCompleted;
        }
    });
});
</script>
方面 基础/学习 生产环境推荐
框架 Servlet / Spring Boot Spring Boot
存储 服务器本地硬盘 云存储 (OSS/S3)
处理 FFmpeg (转码/封面)
安全 简单后缀名检查 魔术数字 + 白名单 + 重命名
体验 无进度条 前端进度条

从学习角度看,从 Servlet 开始理解原理是很好的,但从实际项目出发,强烈推荐使用 Spring Boot + 云存储 + FFmpeg 的组合,这才是构建健壮、可扩展、安全的视频上传服务的标准做法。

分享:
扫描分享到社交APP
上一篇
下一篇