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


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

原创 已于 2024-10-25 12:13:54 修改 · 粉丝可见 · 982 阅读 · 15 · 15 GEO检测 · 编辑
文章链接:https://blog.csdn.net/hacker_51/article/details/143135447

目录

[TOC]

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

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

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

  • 表单中的表单项的 type 必须为 file

  • 表单提交方式必须为 Post 方式

  • 表单属性指定 enctype=”multipart/form-data”

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<!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的起步依赖,以及用于文件上传的支持:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

使用 MultipartFile 接受文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}
}
1
通过Debug可以看到

文件保存的路径,

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 做文件名,这样唯一名,就不会产生文件同名覆盖的情况了。

使用方法:

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 进行配置即可。

配置如下:

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

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

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

文件删除操作

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

删除示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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盘的区分,所以这里我们可以读取当前项目目录将物理存储路径相对应,方便我们读取查看文件。

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

文件工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 获取当前项目放置的路径

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

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

1
2
3
4
5
6
7
8
9
10
11
@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创建目录

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

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

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

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

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

反馈数据流实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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();
}

总结工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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();
}
}

测试访问成功


觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭

微信二维码

wechat pay

支付宝二维码

ali pay

Java文件上传删除相关模块(上传到物理存储)
http://blog.angindem.cn/2024/10/25/Angindem-CSDN博客/169_169/
作者
Angindem
发布于
2024年10月25日
许可协议