Java文件上传删除相关模块(上传到物理存储)

目录

文件上传模块

添加起步依赖

使用 MultipartFile 接受文件

接受文件后transferTo()方法存储到本地目录

处理同文件名产生文件覆盖

UUID(通用唯一识别码)

上传文件过大导致的报错

文件删除操作

通过相对路径读取物理存储文件

文件工具类

使用user.dir得到项目相对路径

打包测试

访问index.html尝试上传文件

Java创建目录

前后端分离,前端读取图片

反馈数据流实例:

总结工具类:


在现代Web应用中,文件上传是一个常见的功能,无论是图片、文档还是其他类型的文件,用户经常需要将它们上传到服务器。而文件上传可以分为两种方式,一种是上传到对象存储OSS,另一种是上传到物理存储。本文将详细介绍如何在Java Web应用中实现文件上传到物理存储模块。

其中实现这个Java文件上传模块后,也需要前端页面的配合。

根据文件上传前端页面三要素:

  • 表单中的表单项的 type 必须为 file
  • 表单提交方式必须为 Post 方式
  • 表单属性指定 enctype="multipart/form-data"

这里讲解的是后端,前端方面可自行简单实现。

这里给出简单文件上传页面

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Angindem File Upload</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <style>
        .container {
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .showImage{
            width: 500px;
            height: 570px;
        }
        .showImage-Child{
            width: 500px;
            height: 570px;
        }
    </style>
</head>

<body>
<div class="container table">
    <div class="context text-center">
        <h1>Angindem File Upload</h1>
        <div class="row">
            <div class="box">
                <div class="thumbnail">
                    <div class="showImage">

                    </div>
                    <div class="caption">
                        <h3>Thumbnail label</h3>
                        <input type="file" id="file">
                        <p>
                            <a href="#" class="btn btn-primary" role="button" style="margin-right: 50px;">上传文件</a>
                            <a href="#" class="btn btn-default" role="button">删除文件</a>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>

<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>

    if (localStorage.getItem('imageUrl') !== null) {
        document.querySelector('.showImage').innerHTML = `<img src="${localStorage.getItem('imageUrl')}" alt="..." class="showImage-Child" style="object-fit: cover">`;
    }

    document.querySelector('.btn-primary').addEventListener('click', async () => {
        if (localStorage.getItem('imageUrl') !== null){
            alert("请先删除已上传的文件,再重新上传");
            return ;
        }
        const fileInput = document.getElementById('file');
        if (fileInput.files.length === 0) {
            alert("请选择上传文件");
            return;
        }
        const formData = new FormData();
        formData.append("file", fileInput.files[0]); // "file" 是后端接收文件的字段名
        const response = await fetch("/upload", {
            method: "POST",
            body: formData // `FormData` 对象会自动设置正确的 `Content-Type` 头
        });
        const result = await response.json();
        console.log(result); // 处理服务器返回的结果
        alert("文件上传成功!!!");
        localStorage.setItem('imageUrl', result.data);
        document.querySelector('.showImage').innerHTML = `<img src="${result.data}" alt="..." class="showImage-Child" style="object-fit: cover">`;
    });

    document.querySelector('.btn-default').addEventListener('click', async () => {
        const imageSrc = document.querySelector('.showImage-Child').src;
        const formData = new FormData();
        formData.append('savePath', imageSrc); // 添加savePath字段到表单数据中

        const response = await fetch("/delFile", {
            method: "POST",
            body: formData // 发送表单数据
        });
        const result = await response.json();
        alert("文件删除成功!!!");
        console.log(result); // 处理服务器返回的结果
        document.querySelector('.showImage').innerHTML = "";
        localStorage.clear();
    });
</script>

</html>

文件上传模块

添加起步依赖

基于Maven的项目,需要在pom.xml文件中添加Spring Boot的起步依赖,以及用于文件上传的支持:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

使用 MultipartFile 接受文件

package com.angindem.controller;
import com.angindem.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
@RestController
public class FileController {

    @PostMapping("/upload")
    public Result upload(MultipartFile images){
        log.info("上传的文件为:{}",images);
        System.out.println("成功接收到文件");
        return Result.success();
    }
}
通过Debug可以看到

文件保存的路径,

显示是TMP临时文件。程序运行完后,TMP文件会自动回收删除。

接受文件后transferTo()方法存储到本地目录

接受到文件后, MultipartFile 自带存储方法,参数为 File 文件对象,通过 File 对象存储到指定位置。

由于File 对象 创建,参数为 其指定存储位置+文件全名.存储文件类型

我们需要知道上传文件的文件类型。所以同时 MultipartFile 自带了getOriginalFilename() 获取原始文件名方法

package com.angindem.controller;

import com.angindem.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Slf4j
@RestController
public class FileController {

    @PostMapping("/upload")
    public Result upload(MultipartFile images) throws IOException {
        log.info("上传的文件为:{}",images);
        System.out.println("成功接收到文件");

        String originalFilename = images.getOriginalFilename(); // 获取原始文件名

        // 存储位置:D:\images\
        String path = "D:\\images\\" + originalFilename;

        // 保存
        images.transferTo(new File(path));

        return Result.success();
    }
}

最后成功存储:

处理同文件名产生文件覆盖

当我们将文件名完全相同文件上传的时候,会发现,同文件名的时候,会发送文件名覆盖。

所以我们这里需要处理同文件名的情况。

UUID(通用唯一识别码)

我们可以通过 UUID 做文件名,这样唯一名,就不会产生文件同名覆盖的情况了。

使用方法:

String uuid = UUID.randomUUID().toString();

调用Java内置UUID工具类即可获得。

同时我们需要获得源文件的文件扩展名。

所以我们对源文件名进行处理即可获得文件扩展名即可。

package com.angindem.controller;

import com.angindem.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@Slf4j
@RestController
public class FileController {

    @PostMapping("/upload")
    public Result upload(MultipartFile images) throws IOException {
        log.info("上传的文件为:{}",images);
        System.out.println("成功接收到文件");

        String originalFilename = images.getOriginalFilename(); // 获取原始文件名

        // 获取 uuid
        String uuid = UUID.randomUUID().toString();

        // 获取文件扩展名下标
        int index = originalFilename.lastIndexOf(".");

        // 截取获得文件扩展名
        String extName = originalFilename.substring(index);

        // 拼凑获得新文件名
        String newFileName = uuid + extName;

        // 存储位置:D:\images\
        String path = "D:\\images\\" + newFileName;

        // 保存
        images.transferTo(new File(path));

        return Result.success();
    }
}

最后效果,可以看到避免了相同文件名覆盖问题:

上传文件过大导致的报错

当我们上传单个文件超过 1M 的时候,可以发现出现了报错。

这是由于在SpringBoot中,文件上传,默认单个文件允许最大大小为1M。如果需要上传大文件,对SpringBoot在  application.properties 进行配置即可。

配置如下:

# 配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB

# 配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

重新上传,可以发现提交成功

文件删除操作

从Java 7开始,java.nio.file包提供了更多的文件操作工具,包括删除文件。使用Files类删除文件通常更推荐,因为它提供了更多的文件操作功能。

删除示例如下:

public static Result delFile(String urlReadPath) {
        String fileName = urlReadPath.split("=")[1];
        String savePath = getSavePath() + fileName;
        Path filePath = null;
        try {
            filePath = Paths.get(savePath);
            if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
                Files.delete(filePath);
                return Result.success();
            }
        }catch (Exception ex){
            return Result.success();
        }
        return Result.success();
    }

测试效果:

点击删除按钮

删除成功!!!

通过相对路径读取物理存储文件

有时候,我们Windows有ABCD盘,当我们开发完成小项目后,需要部署到服务器上,此时服务器上可能就不会有ABCD盘的区分,所以这里我们可以读取当前项目目录将物理存储路径相对应,方便我们读取查看文件。

结合上面,我们可以统一写一个关于文件的工具类

文件工具类

package com.angindem.utils;

import com.angindem.result.Result;
import lombok.Setter;
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;
import java.util.UUID;

public class FileUtils {

    @Setter
    private static String PATH = "D:\\images\\";

    public static String saveFile(MultipartFile images) throws IOException {
        String originalFilename = images.getOriginalFilename();
        String newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String savePath = PATH + newFileName;
        images.transferTo(new File(savePath));
        return savePath;
    }

    public static Result delFile(String savePath) {
        Path filePath = null;
        try {
            filePath = Paths.get(savePath);
            if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
                Files.delete(filePath);
                return Result.success();
            }
        }catch (Exception ex){
            return Result.success();
        }
        return Result.success();
    }
}

使用user.dir得到项目相对路径

我们可以当用户访问主路径的时候调用 System 类使用user.dir获取当前项目放置的路径

// 获取项目主目录路径
String projectPath = System.getProperty("user.dir");

访问主路经,立即获取当前项目运行位置

 @GetMapping("/")
    public Result ProjectMain(){
        // 获取项目主目录
        String projectPath = System.getProperty("user.dir");

        projectPath += "\\images\\";
        log.info("当前项目相对路径:{}",projectPath );

        FileUtils.setPATH(projectPath);
        return Result.success(FileUtils.getPATH());
    }

打包测试

访问主路经可以发现,此时已经获取到了相对项目的文件保存路径。

访问index.html尝试上传文件

发现报错,看来存储文件到指定目录的前提是该目录必须得存在。

我们现在创建一个目录,重新尝试。

文件成功上传!!!

上传操作生效,删除操作同样生效。

Java创建目录

防止一开始的运行找不到目录的报错,这里可以补充创建目录

File dir = new File(PATH);
if(!dir.exists() || !dir.isDirectory()){
    dir.mkdir();
}

前后端分离,前端读取图片

由于绝对路径的存储方式,导致无法读取图片。

这时候,我们需要对后端处理通过数据流的方式反馈给前端。

反馈数据流实例:

@GetMapping("/views")
    public void views(@RequestParam String fileName, HttpServletResponse response) throws IOException {

        //"http://xxxx/views?fileName=xxx"
        if (fileName == null) return ;  // 当空请求,不响应输出流

        String[] split = fileName.split("=");   // 获取到文件URL后,通过分割字符串获取文件名

        String name = "/" + split[split.length-1];  // 最后一个字符串就是文件名

        File file = new File(FileUtils.getSavePath() + name);   // 根据绝对路径读取文件 + 文件名

        //设置输出流格式
        ServletOutputStream outputStream = response.getOutputStream();

        response.addHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(name,"UTF-8"));

        //任意类型的二进制流数据
        response.setContentType("application/octet-stream");

        //读取文件字节流
        outputStream.write(FileUtil.readBytes(file));

        // 清除刷新缓冲区输出流
        outputStream.flush();

        // 关闭输出流
        outputStream.close();
    }

总结工具类:

package com.angindem.utils;

import com.angindem.result.Result;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
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;
import java.util.UUID;

@Slf4j
public class FileUtils {

    @Setter
    @Getter
    private static String PATH = "D:\\images\\";

    public static String getSavePath() {
        // 获取项目主目录
        String projectPath = System.getProperty("user.dir");
        // 处理 \ 转义字符
//        projectPath = projectPath.replace("\\", "\\\\") + "\\images\\";
        projectPath += "\\images\\";
        log.info("当前项目相对路径:{}", projectPath);
        PATH = projectPath;
        return PATH;
    }

    public static String saveFile(MultipartFile images) throws IOException {
        getSavePath();
        String originalFilename = images.getOriginalFilename();
        String newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String savePath = PATH + newFileName;

        File dir = new File(PATH);
        if (!dir.exists() || !dir.isDirectory()) {
            dir.mkdir();
        }
        images.transferTo(new File(savePath));
        return newFileName;
    }

    public static Result delFile(String urlReadPath) {
        String fileName = urlReadPath.split("=")[1];
        String savePath = getSavePath() + fileName;
        Path filePath = null;
        try {
            filePath = Paths.get(savePath);
            if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
                Files.delete(filePath);
                return Result.success();
            }
        }catch (Exception ex){
            return Result.success();
        }
        return Result.success();
    }
}

测试访问成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值