图片上传教程

在开发过程中,经常会遇到需要上传图片和视频的需求。本文将为你详细介绍如何实现一个图片和视频上传功能。

一、技术选型

  1. 后端技术
    • Spring Boot:构建高效、便捷的后端服务。
    • MyBatis-Plus:简化数据库操作。
    • Druid:强大的数据库连接池。
    • Lombok:简化 Java 代码。
  1. 前端技术:使用 HTML、JavaScript 和 jQuery 构建简单的测试页面。

二、功能实现步骤

(一)后端实现

  1. 创建通用结果返回类
    • 创建一个名为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 +
        '}';
    }
  1. 定义结果码枚举类
    • 定义一个名为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;
    }
}
  1. 创建文件上传工具类
    • 创建一个名为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");
    }
}
  1. 创建文件上传控制器
    • 创建一个名为FileUploadController的控制器类。
    • 通过@Autowired注解注入FileUploadUtil工具类。
    • 提供uploadFile方法,用于处理文件上传请求。该方法接受HttpServletRequest请求对象和MultipartFile文件对象作为参数。
    • 在方法中,首先检查文件是否为空。如果为空,返回相应的错误结果。
    • 接着,根据文件类型选择保存的相对路径。如果文件类型不支持,返回相应的错误结果。
    • 最后,调用FileUploadUtiluploadFile方法,返回上传结果。
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;
    }
}
  1. 配置路径映射和跨域
    • 创建一个名为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);
    }

}

(二)配置文件设置

  1. 端口设置:在配置文件中设置应用服务的 WEB 访问端口,例如server.port=8569
  2. 数据库连接设置:配置数据库连接信息,包括 URL、用户名、密码、驱动类名等。
  3. MyBatis-Plus 配置:配置mapper.xml文件位置、打印 SQL、主键自增策略、实体类包名、驼峰转换、逻辑删除字段等。
  4. 文件上传设置:设置文件上传地址和大小限制,例如file-save-path=F:/images/spring.servlet.multipart.max-file-size=5MBspring.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>

(三)前端测试页面

  1. 创建一个 HTML 页面,包含一个文件选择区域和上传按钮。
  2. 使用 jQuery 和 AJAX 实现文件上传功能。当用户点击上传按钮时,获取选择的文件,创建FormData对象,将文件添加到其中,并发送 POST 请求到后端接口。
  3. 在请求成功和失败时,分别将响应结果显示在页面上。
<!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>

三、测试与使用

  1. 启动后端服务。
  2. 打开前端测试页面,选择一个图片或视频文件,点击上传按钮。
  3. 查看后端控制台输出的文件信息和上传结果。
  4. 如果上传成功,可以在浏览器中访问返回的文件地址,例如http://localhost:8569/images/[文件名],查看上传的文件。

eg:http://localhost:8569/images/6ce4b1a06ecc4090b55866f7aa5f3b25.png

通过以上步骤,你可以实现一个简单的图片和视频上传功能。在实际应用中,可以根据需求进行进一步的扩展和优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值