在 Web 开发中,视频文件上传与存储是常见需求,如教育平台的课程视频、社交平台的用户分享视频等。本文将讲解如何使用 Java SpringBoot 构建后端接口,配合 Vue 前端框架实现视频文件的上传、进度展示与服务器存储功能。

一、技术栈选型

在开始开发前,先明确本次使用的技术栈,确保前后端技术适配:

  • 后端:Java 11+、SpringBoot 2.7.x、Spring MVC(处理请求)、Commons FileUpload(文件解析)、Lombok(简化代码)
  • 前端:Vue 3(组合式 API)、Vite(构建工具)、Axios(发送 HTTP 请求)、Element Plus(UI 组件,用于上传组件和进度条)
  • 存储方式:本次采用服务器本地存储(后续可扩展为 MinIO、OSS 等云存储)
  • 开发工具:IntelliJ IDEA(后端)、Visual Studio Code(前端)、Postman(接口测试)

二、后端开发:SpringBoot 接口实现

后端核心任务是接收前端传来的视频文件,进行合法性校验(大小、格式),然后保存到服务器指定目录,并返回上传结果。

2.1 初始化 SpringBoot 项目

  1. 打开 IntelliJ IDEA,通过「Spring Initializr」创建新项目,选择以下依赖:
    • Spring Web(提供 HTTP 接口能力)
    • Lombok(减少模板代码)
    • Spring Boot DevTools(热部署,提高开发效率)
  1. 项目结构规划

com.example.videoupload

├── config          # 配置类(如文件上传配置)

├── controller      # 控制器(接收前端请求)

├── exception       # 自定义异常(如文件过大、格式错误)

├── service         # 业务逻辑层(文件处理)

├── util            # 工具类(如文件路径处理)

└── VideoUploadApplication.java  # 启动类

2.2 核心配置:文件上传参数配置

创建config/FileUploadConfig.java,配置文件上传的最大大小、临时目录等参数(默认 SpringBoot 文件上传大小限制较小,需手动调整):

package com.example.videoupload.config;

import org.springframework.boot.web.servlet.MultipartConfigFactory;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.util.unit.DataSize;

import javax.servlet.MultipartConfigElement;

@Configuration

public class FileUploadConfig {

    @Bean

    public MultipartConfigElement multipartConfigElement() {

        MultipartConfigFactory factory = new MultipartConfigFactory();

        

        // 1. 设置单个文件最大大小(此处为500MB,可根据需求调整)

        factory.setMaxFileSize(DataSize.ofMegabytes(500));

        // 2. 设置一次请求所有文件的总大小(此处为1GB)

        factory.setMaxRequestSize(DataSize.ofGigabytes(1));

        // 3. 设置临时文件存储目录(默认在系统临时目录,也可自定义)

        // factory.setLocation("D:/temp/video-upload");

        

        return factory.createMultipartConfig();

    }

}

2.3 自定义异常:处理文件上传错误

创建exception/VideoUploadException.java,用于统一处理文件上传中的异常(如格式不支持、文件过大):

package com.example.videoupload.exception;

import lombok.Getter;

@Getter

public class VideoUploadException extends RuntimeException {

    // 错误码(可用于前端区分错误类型)

    private final Integer code;

    public VideoUploadException(String message, Integer code) {

        super(message);

        this.code = code;

    }

    // 常见异常静态方法(简化调用)

    public static VideoUploadException fileTooLarge() {

        return new VideoUploadException("视频文件过大,最大支持500MB", 413);

    }

    public static VideoUploadException unsupportedFormat() {

        return new VideoUploadException("不支持的视频格式,仅支持MP4、AVI、MOV", 415);

    }

}

再创建exception/GlobalExceptionHandler.java,全局捕获异常并返回统一格式:

package com.example.videoupload.exception;

import lombok.Data;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseStatus;

import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice

public class GlobalExceptionHandler {

    // 捕获自定义的视频上传异常

    @ExceptionHandler(VideoUploadException.class)

    public Result handleVideoUploadException(VideoUploadException e) {

        return new Result(e.getCode(), e.getMessage(), null);

    }

    // 捕获SpringBoot默认的文件过大异常(防止漏捕)

    @ExceptionHandler(org.springframework.web.multipart.MaxUploadSizeExceededException.class)

    @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)

    public Result handleMaxSizeException() {

        return new Result(413, "视频文件过大,最大支持500MB", null);

    }

    // 统一返回格式封装

    @Data

    public static class Result {

        private Integer code;

        private String message;

        private Object data;

        public Result(Integer code, String message, Object data) {

            this.code = code;

            this.message = message;

            this.data = data;

        }

    }

}

2.4 业务逻辑:文件处理服务

创建service/VideoUploadService.java,封装视频文件的校验、保存逻辑:

package com.example.videoupload.service;

import com.example.videoupload.exception.VideoUploadException;

import com.example.videoupload.util.FileUtil;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Service;

import org.springframework.web.multipart.MultipartFile;

import java.io.File;

import java.io.IOException;

import java.util.Arrays;

import java.util.List;

@Service

@Slf4j

public class VideoUploadService {

    // 从配置文件读取视频存储根路径(建议在application.properties中配置)

    @Value("${video.upload.base-path}")

    private String basePath;

    // 支持的视频格式列表

    private final List<String> SUPPORTED_FORMATS = Arrays.asList("mp4", "avi", "mov");

    /**

     * 视频文件上传核心方法

     * @param file 前端传来的MultipartFile对象

     * @return 保存后的文件访问路径(如:/videos/20251031/abc123.mp4)

     */

    public String uploadVideo(MultipartFile file) {

        // 1. 校验文件是否为空

        if (file.isEmpty()) {

            throw new VideoUploadException("上传的视频文件为空", 400);

        }

        // 2. 校验文件格式

        String originalFilename = file.getOriginalFilename();

        String fileSuffix = FileUtil.getFileSuffix(originalFilename); // 自定义工具类,获取文件后缀

        if (!SUPPORTED_FORMATS.contains(fileSuffix.toLowerCase())) {

            throw VideoUploadException.unsupportedFormat();

        }

        // 3. 生成唯一文件名(避免重复覆盖,使用UUID+后缀)

        String uniqueFileName = FileUtil.generateUniqueFileName(fileSuffix);

        // 4. 构建文件存储路径(按日期分目录,如:basePath/20251031/uniqueFileName)

        String dateDir = FileUtil.getDateDir(); // 格式:yyyyMMdd

        String saveDirPath = basePath + File.separator + dateDir;

        File saveDir = new File(saveDirPath);

        // 5. 若目录不存在,创建目录

        if (!saveDir.exists()) {

            boolean mkdirs = saveDir.mkdirs();

            if (!mkdirs) {

                log.error("创建视频存储目录失败:{}", saveDirPath);

                throw new VideoUploadException("服务器存储目录创建失败", 500);

            }

        }

        // 6. 保存文件到指定路径

        File saveFile = new File(saveDir, uniqueFileName);

        try {

            file.transferTo(saveFile); // 核心方法:将MultipartFile写入本地文件

            log.info("视频文件上传成功,保存路径:{}", saveFile.getAbsolutePath());

            // 7. 返回文件访问路径(后续可配置静态资源映射,让前端通过URL访问)

            return "/videos/" + dateDir + "/" + uniqueFileName;

        } catch (IOException e) {

            log.error("视频文件保存失败", e);

            throw new VideoUploadException("服务器保存文件失败", 500);

        }

    }

}

2.5 工具类:文件路径与名称处理

创建util/FileUtil.java,封装常用的文件操作工具方法:

package com.example.videoupload.util;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.UUID;

public class FileUtil {

    /**

     * 获取文件后缀(如:"test.mp4" -> "mp4")

     */

    public static String getFileSuffix(String originalFilename) {

        if (originalFilename == null || !originalFilename.contains(".")) {

            return "";

        }

        return originalFilename.substring(originalFilename.lastIndexOf(".") + 1);

    }

    /**

     * 生成唯一文件名(UUID + 后缀)

     */

    public static String generateUniqueFileName(String suffix) {

        String uuid = UUID.randomUUID().toString().replace("-", "");

        return suffix.isEmpty() ? uuid : uuid + "." + suffix;

    }

    /**

     * 获取日期目录(格式:yyyyMMdd)

     */

    public static String getDateDir() {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

        return sdf.format(new Date());

    }

}

2.6 控制器:接收前端请求

创建controller/VideoUploadController.java,提供 POST 接口供前端调用:

package com.example.videoupload.controller;

import com.example.videoupload.exception.GlobalExceptionHandler;

import com.example.videoupload.service.VideoUploadService;

import lombok.RequiredArgsConstructor;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.multipart.MultipartFile;

@RestController

@RequestMapping("/api/video")

@RequiredArgsConstructor // Lombok注解,自动注入依赖

public class VideoUploadController {

    private final VideoUploadService videoUploadService;

    /**

     * 视频上传接口

     * @param file 前端传来的视频文件(参数名需与前端一致)

     * @return 统一返回格式(包含访问路径)

     */

    @PostMapping("/upload")

    public GlobalExceptionHandler.Result upload(@RequestParam("videoFile") MultipartFile file) {

        String accessPath = videoUploadService.uploadVideo(file);

        return new GlobalExceptionHandler.Result(200, "视频上传成功", accessPath);

    }

}

2.7 配置静态资源映射与基础路径

application.properties中添加配置,指定视频存储路径,并配置静态资源映射(让前端能通过 URL 直接访问上传的视频):

# 视频存储根路径(Windows用D:/,Linux用/opt/)

video.upload.base-path=D:/video-storage

# 静态资源映射:将URL中的"/videos/**"映射到本地的视频存储路径

spring.web.resources.static-locations=file:${video.upload.base-path}/,classpath:/static/

spring.mvc.static-path-pattern=/videos/**

2.8 后端测试:用 Postman 验证接口

此时后端接口已开发完成,可通过 Postman 测试:

  1. 打开 Postman,创建 POST 请求,URL 为http://localhost:8080/api/video/upload
  2. 在「Body」中选择「form-data」,Key 填写videoFile(与 Controller 参数名一致),Value 选择一个 MP4 视频文件
  3. 点击发送,若返回以下结果,则说明接口正常:

{

    "code": 200,

    "message": "视频上传成功",

    "data": "/videos/20251031/123e4567e89b12d3a456426614174000.mp4"

}

  1. 同时查看本地D:/video-storage/20251031/目录,会发现视频文件已保存成功。

(配图 1:Postman 测试视频上传接口的截图,标注请求方式、URL、参数、返回结果)

三、前端开发:Vue 上传页面实现

前端核心任务是创建上传表单,支持视频文件选择、上传进度展示、上传结果提示,并调用后端接口完成文件提交。

3.1 初始化 Vue 项目

  1. 打开 Visual Studio Code,通过终端执行以下命令创建 Vue 3 项目:

# 安装Vite(若未安装)

npm install -g create-vite

# 创建项目(项目名:video-upload-frontend)

create-vite video-upload-frontend --template vue

# 进入项目目录

cd video-upload-frontend

# 安装依赖

npm install

# 安装Element Plus和Axios

npm install element-plus @element-plus/icons-vue axios

  1. 项目结构规划(核心文件)

src/

├── components/      # 组件(上传组件)

│   └── VideoUpload.vue  # 视频上传组件

├── utils/           # 工具类(Axios配置)

│   └── request.js   # Axios请求封装

├── App.vue          # 根组件

└── main.js          # 入口文件

3.2 Axios 配置:请求封装

创建src/utils/request.js,封装 Axios 请求,处理请求拦截、响应拦截、错误提示:

import axios from 'axios';

import { ElMessage, ElLoading } from 'element-plus';

// 创建Axios实例

const request = axios.create({

  baseURL: 'http://localhost:8080/api', // 后端接口基础路径

  timeout: 600000, // 超时时间(10分钟,适配大视频上传)

  headers: {

    'Content-Type': 'multipart/form-data' // 视频上传需用form-data格式

  }

});

// 请求拦截器:添加loading(可选)

let loadingInstance;

request.interceptors.request.use(

  (config) => {

    // 显示loading(上传大文件时提示用户)

    loadingInstance = ElLoading.service({

      text: '视频上传中,请稍候...',

      background: 'rgba(0, 0, 0, 0.5)'

    });

    return config;

  },

  (error) => {

    loadingInstance.close();

    ElMessage.error('请求发送失败,请重试');

    return Promise.reject(error);

  }

);

// 响应拦截器:处理返回结果

request.interceptors.response.use(

  (response) => {

    loadingInstance.close();

    const res = response.data;

    // 若返回码不是200,提示错误

    if (res.code !== 200) {

      ElMessage.error(res.message || '操作失败');

      return Promise.reject(new Error(res.message || 'Error'));

    } else {

      ElMessage.success(res.message || '操作成功');

      return res.data; // 返回数据部分(如访问路径)

    }

  },

  (error) => {

    loadingInstance.close();

    // 处理413(文件过大)、415(格式错误)等状态码

    const status = error.response?.status;

    if (status === 413) {

      ElMessage.error('视频文件过大,最大支持500MB');

    } else if (status === 415) {

      ElMessage.error('不支持的视频格式,仅支持MP4、AVI、MOV');

    } else {

      ElMessage.error('服务器异常,请联系管理员');

    }

    return Promise.reject(error);

  }

);

export default request;

3.3 视频上传组件:核心功能实现

创建src/components/VideoUpload.vue,使用 Element Plus 的Upload组件实现文件选择、进度条展示,并调用封装好的 Axios 请求:

<template>

  <div class="video-upload-container">

    <h2>视频文件上传</h2>

    <!-- Element Plus Upload组件 -->

    <el-upload

      class="upload-demo"

      action="#" <!-- 此处action设为空,通过http-request自定义请求 -->

      :http-request="handleUpload" <!-- 自定义上传方法 -->

      :file-list</doubaocanvas>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐