在开发过程中,经常会遇到需要上传图片和视频的需求。本文将为你详细介绍如何实现一个图片和视频上传功能。
一、技术选型
- 后端技术:
-
- Spring Boot:构建高效、便捷的后端服务。
- MyBatis-Plus:简化数据库操作。
- Druid:强大的数据库连接池。
- Lombok:简化 Java 代码。
- 前端技术:使用 HTML、JavaScript 和 jQuery 构建简单的测试页面。
二、功能实现步骤
(一)后端实现
- 创建通用结果返回类
-
- 创建一个名为
R
的通用结果返回类,用于封装后端返回给前端的响应结果。它包含返回码、返回消息和返回数据三个属性,并提供了一系列方法用于构建和操作返回结果。 - 例如,可以使用
R.ok(data)
方法快速构建一个成功的返回结果,其中data
为返回的数据。
- 创建一个名为
import java.util.Objects;
/**
* 通用的结果返回类,用于封装后端返回给前端的响应结果
*/
public class R<T> {
// 返回码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
// 私有化构造方法,用于内部构建对象
private R(Integer code, String message, T data) {
this.code = validateCode(code);
this.message = validateMessage(message);
this.data = data;
}
// 验证返回码
private static Integer validateCode(Integer code) {
if (code == null || code < 0 || code > 999) {
throw new IllegalArgumentException("无效的返回码,必须在 0 到 999 之间");
}
return code;
}
// 验证返回消息
private static String validateMessage(String message) {
if (message == null || message.isEmpty()) {
throw new IllegalArgumentException("返回消息不能为空");
}
return message;
}
// 构建自定义返回结果的方法
public static <T> R<T> build(T body, Integer code, String message) {
return new R<>(code, message, body);
}
/**
* 操作成功的快捷方法
* @param data 成功时返回的数据,可为空
* @param <T> 泛型参数,表示返回的数据类型
* @return 返回构建的 R 对象
*/
public static <T> R<T> ok(T data) {
return build(data, ResultCodeEnum.OK.getCode(), ResultCodeEnum.OK.getMessage());
}
/**
* 操作失败的快捷方法
* @param errorCode 错误码枚举
* @param data 失败时返回的数据,可为空
* @param <T> 泛型参数,表示返回的数据类型
* @return 返回构建的 R 对象
*/
public static <T> R<T> failure(ResultCodeEnum errorCode, T data) {
return build(data, errorCode.getCode(), errorCode.getMessage());
}
// setter 方法用于链式调用
public R<T> message(String msg) {
this.message = validateMessage(msg);
return this;
}
public R<T> code(Integer code) {
this.code = validateCode(code);
return this;
}
// getter 和 setter 方法
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof R)) return false;
R<?> result = (R<?>) o;
return Objects.equals(code, result.code) && Objects.equals(message, result.message) && Objects.equals(data, result.data);
}
@Override
public int hashCode() {
return Objects.hash(code, message, data);
}
@Override
public String toString() {
return "R{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
- 定义结果码枚举类
-
- 定义一个名为
ResultCodeEnum
的枚举类,用于表示各种结果码和对应的消息。例如,OK(200, "请求成功")
表示成功的结果码和消息。
- 定义一个名为
package com.example.demo.demos.until;
/**
* @author Ash
*/
public enum ResultCodeEnum {
// Success
OK(200, "请求成功"),
CREATED(201, "资源创建成功"),
NO_CONTENT(204, "无内容"),
// Redirection
MOVED_PERMANENTLY(301, "资源已被永久移动"),
FOUND(302, "临时性重定向"),
// Client Errors
BAD_REQUEST(400, "客户端请求错误"),
UNAUTHORIZED(401, "未授权"),
FORBIDDEN(403, "禁止访问"),
NOT_FOUND(404, "未找到资源"),
// Server Errors
INTERNAL_SERVER_ERROR(500, "内部服务器错误"),
BAD_GATEWAY(502, "无效网关"),
SERVICE_UNAVAILABLE(503, "服务不可用"),
// Custom Errors
USERNAME_ERROR(1001, "用户名错误"),
PASSWORD_ERROR(1003, "密码错误"),
NOT_LOGIN(1004, "登录已过期"),
USERNAME_USED(1005, "用户名被占用");
private final Integer code;
private final String message;
ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
- 创建文件上传工具类
-
- 创建一个名为
FileUploadUtil
的组件类,用于处理文件上传。 - 该工具类通过
@Value
注解从配置文件中获取文件保存路径。 - 提供
uploadFile
方法,用于上传文件。该方法接受HttpServletRequest
请求对象、MultipartFile
文件对象和文件保存的相对路径作为参数。 - 在方法中,首先检查文件是否为空和文件类型是否为图片或视频。如果文件为空或类型不支持,返回相应的错误结果。
- 接着,生成唯一的文件名,确定文件保存路径,并创建目录(如果不存在)。
- 最后,将上传的文件保存到目标路径,并返回文件的相对路径作为上传结果。
- 创建一个名为
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import com.example.demo.demos.until.R;
import com.example.demo.demos.until.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件上传工具类,支持图片和视频文件
* @author Ash
*/
@Component
public class FileUploadUtil {
// 文件保存路径,通过应用属性配置文件注入
@Value("${file-save-path}")
private String fileSavePath;
/**
* 上传文件方法
*
* @param request 请求对象,用于获取上下文信息(本例未使用)
* @param file 需要上传的文件
* @param position 文件保存的相对路径,如目录名
* @return 返回上传结果,封装为 R 对象
*/
public R<String> uploadFile(HttpServletRequest request, MultipartFile file, String position) {
// 检查文件是否为空
if (file == null || file.isEmpty()) {
// 返回错误代码,表示文件为空
return R.failure(ResultCodeEnum.NO_CONTENT, "文件为空");
}
// 检查文件类型是否为图片或视频
String fileType = file.getContentType();
if (fileType == null || !(isImage(fileType) || isVideo(fileType))) {
// 返回错误代码,表示文件类型不支持
return R.failure(ResultCodeEnum.BAD_REQUEST, "传入的文件格式不支持");
}
// 生成新的文件名,使用UUID确保文件名唯一性
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
String newFileName = UUID.randomUUID().toString().replaceAll("-", "") + suffix;
// 确定文件保存路径,并创建目录(如果不存在)
String savePath = fileSavePath + position;
File saveDir = new File(savePath);
if (!saveDir.exists()) {
saveDir.mkdirs();
}
// 目标文件
File saveFile = new File(savePath + File.separator + newFileName);
try {
// 将上传的文件保存到目标路径
file.transferTo(saveFile);
} catch (IOException e) {
e.printStackTrace();
// 返回错误代码,表示文件保存失败
return R.failure(ResultCodeEnum.INTERNAL_SERVER_ERROR, "文件保存失败");
}
// 返回文件的相对路径,用于后续访问
String url = position + newFileName;
return R.ok(url);
}
/**
* 判断文件是否为图片类型
* @param fileType 文件类型
* @return 如果是图片类型返回 true,否则返回 false
*/
private boolean isImage(String fileType) {
return fileType.endsWith("jpg") || fileType.endsWith("jpeg") ||
fileType.endsWith("png") || fileType.endsWith("gif");
}
/**
* 判断文件是否为视频类型
* @param fileType 文件类型
* @return 如果是视频类型返回 true,否则返回 false
*/
private boolean isVideo(String fileType) {
return fileType.endsWith("mp4") || fileType.endsWith("avi") ||
fileType.endsWith("mov") || fileType.endsWith("mkv");
}
}
- 创建文件上传控制器
-
- 创建一个名为
FileUploadController
的控制器类。 - 通过
@Autowired
注解注入FileUploadUtil
工具类。 - 提供
uploadFile
方法,用于处理文件上传请求。该方法接受HttpServletRequest
请求对象和MultipartFile
文件对象作为参数。 - 在方法中,首先检查文件是否为空。如果为空,返回相应的错误结果。
- 接着,根据文件类型选择保存的相对路径。如果文件类型不支持,返回相应的错误结果。
- 最后,调用
FileUploadUtil
的uploadFile
方法,返回上传结果。
- 创建一个名为
mport com.example.demo.demos.until.FileUploadUtil;
import com.example.demo.demos.until.R;
import com.example.demo.demos.until.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
* 文件上传控制器
* @author Ash
*/
@RestController
public class FileUploadController {
@Autowired
private FileUploadUtil fileUploadUtil;
@PostMapping("/upload")
public R<String> uploadFile(HttpServletRequest request, @RequestParam(value = "file", required = false) MultipartFile file) {
System.out.println("FileUploadController.uploadFile");
if (file == null || file.isEmpty()) {
System.out.println("Received null or empty file.");
return R.failure(ResultCodeEnum.BAD_REQUEST, "没有检测到上传的文件");
}
// 打印文件的详细信息,用于调试,正式环境中应该去除
System.out.println("File Name: " + file.getOriginalFilename());
System.out.println("File Size: " + file.getSize());
System.out.println("File Content Type: " + file.getContentType());
// 根据文件类型选择保存的相对路径
String relativeDirectory;
String fileType = file.getContentType();
if (fileType != null && (fileType.endsWith("jpg") || fileType.endsWith("jpeg") ||
fileType.endsWith("png") || fileType.endsWith("gif"))) {
relativeDirectory = "images/";
} else if (fileType != null && (fileType.endsWith("mp4") || fileType.endsWith("avi") ||
fileType.endsWith("mov") || fileType.endsWith("mkv"))) {
relativeDirectory = "videos/";
} else {
return R.failure(ResultCodeEnum.BAD_REQUEST, "不支持的文件类型");
}
// 调用文件上传工具类,返回上传结果
R<String> result = fileUploadUtil.uploadFile(request, file, relativeDirectory);
// 返回结果
return result;
}
}
- 配置路径映射和跨域
-
- 创建一个名为
WebConfig
的配置类,实现WebMvcConfigurer
接口。 - 在
addResourceHandlers
方法中,配置图片和视频资源的处理,将请求路径映射到文件保存路径。 - 在
addCorsMappings
方法中,配置跨域访问规则,允许来自任意域名的请求,并设置允许的 HTTP 方法、头部信息等。
- 创建一个名为
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author Ash
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${file-save-path}")
private String fileSavePath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置图片资源处理
registry.addResourceHandler("/images/**")
.addResourceLocations("file:" + fileSavePath + "images/");
// 配置视频资源处理
registry.addResourceHandler("/videos/**")
.addResourceLocations("file:" + fileSavePath + "videos/");
}
/**
* 配置CORS策略。用于配置跨域访问规则。
*
* @param registry CORS注册器,用于添加CORS映射。
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加CORS映射规则
// 配置所有接口都允许CORS请求
registry.addMapping("/**")
// 配置所有接口都允许CORS请求
.allowCredentials(true)
// 允许来自任意域名的请求
.allowedOriginPatterns("*")
// 允许的HTTP方法
.allowedMethods("GET", "POST", "PUT", "DELETE")
// 允许客户端发送任意头部信息
.allowedHeaders("*")
// 暴露任意头部信息给客户端
.exposedHeaders("*")
// 预检请求的有效期,单位为秒
.maxAge(3600);
}
}
(二)配置文件设置
- 端口设置:在配置文件中设置应用服务的 WEB 访问端口,例如
server.port=8569
。 - 数据库连接设置:配置数据库连接信息,包括 URL、用户名、密码、驱动类名等。
- MyBatis-Plus 配置:配置
mapper.xml
文件位置、打印 SQL、主键自增策略、实体类包名、驼峰转换、逻辑删除字段等。 - 文件上传设置:设置文件上传地址和大小限制,例如
file-save-path=F:/images/
,spring.servlet.multipart.max-file-size=5MB
,spring.servlet.multipart.max-request-size=10MB
。
# 应用服务 WEB 访问端口
server.port=8569
server.tomcat.uri-encoding=UTF-8
#配置德鲁伊连接池
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#德鲁伊连接池超时时间
spring.datasource.druid.max-wait=60000
# mybatis-plus 配置
# 配置mapper.xml文件位置
mybatis-plus.mapper-locations=classpath:/mapper/**/*.xml
# 配置mybatis打印SQL
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 全局配置mybatis主键自增策略
mybatis-plus.global-config.db-config.id-type=auto
# 配置mybatis-plus实体类的包名
mybatis-plus.type-aliases-package=com.example.comprehensive.entity
# 配置mybatis-plus驼峰转换
mybatis-plus.configuration.map-underscore-to-camel-case=true
#逻辑删除字段配置
mybatis-plus.global-config.db-config.logic-delete-field=isDelete
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
#控制台mybatis-plus标记
mybatis-plus.global-config.banner=true
#文件上传地址
file-save-path=F:/images/
#文件上传大小限制
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=10MB
# 数据库时间格式
#spring.jackson.date-format=yyyy-MM-dd
#spring.jackson.time-zone=Asia/Shanghai
依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.czy</groupId>
<artifactId>comprehensive
</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bookTest02</name>
<description>comprehensive
</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<!--spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!---->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.12</version>
</dependency>
<!-- Spring注解化实现参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Boot AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Hutton工具类库:图形验证码生成、加解密、简单http请求、类拷贝等 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<!-- Druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.20</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.comprehensive
.comprehensive
Application
</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
(三)前端测试页面
- 创建一个 HTML 页面,包含一个文件选择区域和上传按钮。
- 使用 jQuery 和 AJAX 实现文件上传功能。当用户点击上传按钮时,获取选择的文件,创建
FormData
对象,将文件添加到其中,并发送 POST 请求到后端接口。 - 在请求成功和失败时,分别将响应结果显示在页面上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>File Upload Test Page</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
.container {
max-width: 600px;
margin: auto;
padding: 20px;
}
.upload-area {
border: 1px dashed #ccc;
padding: 20px;
text-align: center;
}
.upload-area input[type="file"] {
display: none;
}
.upload-area label {
cursor: pointer;
padding: 10px 20px;
background-color: #007bff;
color: white;
}
</style>
</head>
<body>
<div class="container">
<h1>文件上传测试页面</h1>
<div class="upload-area">
<label for="file-input">选择图片文件</label>
<input type="file" id="file-input" accept="image/*" />
<button id="upload-button">上传</button>
</div>
<pre id="response"></pre>
</div>
<script>
$(document).ready(function () {
// 当用户点击上传按钮时触发
$("#upload-button").click(function () {
var fileInput = $("#file-input")[0];
if (fileInput.files.length === 0) {
alert("请选择一个文件!");
return;
}
var file = fileInput.files[0];
var formData = new FormData();
formData.append("file", file);
$.ajax({
url: "http://localhost:8569/upload", // 指向您的后端接口
type: "POST",
data: formData,
processData: false, // 不对数据进行处理
contentType: false, // 不设置内容类型
success: function (response) {
$("#response").text(JSON.stringify(response, null, 4));
},
error: function (error) {
$("#response").text(JSON.stringify(error.responseJSON, null, 4));
},
});
});
});
</script>
</body>
</html>
三、测试与使用
- 启动后端服务。
- 打开前端测试页面,选择一个图片或视频文件,点击上传按钮。
- 查看后端控制台输出的文件信息和上传结果。
- 如果上传成功,可以在浏览器中访问返回的文件地址,例如
http://localhost:8569/images/[文件名]
,查看上传的文件。
eg:http://localhost:8569/images/6ce4b1a06ecc4090b55866f7aa5f3b25.png
通过以上步骤,你可以实现一个简单的图片和视频上传功能。在实际应用中,可以根据需求进行进一步的扩展和优化。