目录
概述
前端编写-上传表单和图片回显
HTML表单代码
发送请求逻辑
CSS代码
后端编写-文件上传接口
后端编写-文件下载接口
概述
在现代Web应用程序中,文件上传和下载是常见的功能。本博客将介绍如何使用原生JS和Spring Boot实现文件上传和下载的功能。
在其中会介绍到前端使用表单做文件上传时需要遵循的规则,并使用SpringBoot框架编写后端API接口来处理传入的文件,以及使用原生JS和SpringBoot框架实现文件下载功能。
测试:本篇博客主要使用图片的上传和回显作为重点测试介绍。
目的:实现类似头像上传功能效果。
整体功能逻辑:前端上传图片,后端接收到后存储到本地服务器并返回给前端存储的文件名,前端想要获取(下载)图片时,发送请求携带文件名,后端接收到后以流的方式响应给前端做展示。
前端编写-上传表单和图片回显
功能界面展示
选择并提交图片回显后展示
HTML表单代码
// 表单容器
<div id="container">
<form id="myForm" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file"><br><br>
<input type="submit" value="上传文件">
</form>
</div>
<br>
<!-- 图片回显 -->
<div id="pictureEcho">
</div>
代码说明
想要实现文件上传,表单书写时基本的3点要求:
【1】请求方式method需要为post
【2】表单数据的编码类型enctype为multipart/form-data
【3】需要一个类型type为file的控件<input>上传文件
发送请求逻辑
<script>
// 获取表单节点
let form = document.getElementById('myForm');
// 获取图片回显容器节点
let pictureEchoContainer = document.querySelector("#pictureEcho");
// 监听表单的submit提交事件
form.addEventListener('submit', function(event) {
// submit事件触发时阻止默认事件,防止页面跳转刷新
event.preventDefault();
// 创建xhr对象,用于发送请求
var xhr = new XMLHttpRequest();
// 指定请求方式和请求路径
xhr.open('POST', '/files/upload');
// 使用formData对象处理表单数据
var formData = new FormData(form);
// 上传完成后的处理
xhr.onload = function() {
// 响应状态码为200表示请求成功
if (xhr.status === 200) {
// 调用图片回显的回调函数,将返回的随机UUID名称作为参数
pictureShowCallback(xhr.responseText);
}
};
// 发送HTTP请求并将数据发送给服务器
xhr.send(formData);
});
// 自定义回调函数
function pictureShowCallback(pictureName){
// 创建img节点
let img = document.createElement("img")
// 将img节点添加到div容器中
pictureEchoContainer.append(img)
// 给img节点添加src属性
img.src = "http://127.0.0.1:8080/files/download/" + pictureName;
}
</script>
代码说明
具体步骤说明在代码示例中已有详解,主要过程为当点击了提交按钮,触发submit事件后,会向指定服务器发送请求,并且编写了获取服务器返回值的代码,当请求成功后调用自定义回调函数,在其中动态创建img节点,并向img节点中设置src属性为请求路径。当img节点有了src属性后,会向其src的值发送请求获取图片自动展示。
其中的xhr.onload为请求完成时调用的函数,在此用于接收返回值,获取上传后服务器给图片随机生成的UUID名称,下面做图片回显(文件下载)的时候就可以根据此UUID名称发送请求了。
CSS代码
#container{
border: solid #253bbd 2px;
border-radius: 5px;
width: 190px;
height: 90px;
padding: 20px;
background-color: #6a9cd3;
}
img{
position: absolute;
width: 230px;
height: 130px;
top: 10px;
left: 10px;
border-radius: 5px;
/*border: solid red;*/
}
后端编写-文件上传接口
当我们在前端编写完请求发送逻辑以后,我们就需要有一个后端接口来接收处理发送过来的请求数据。
/**
* Date:2023/3/6
* author:zmh
* description:文件上传和下载相关接口
**/
@RestController
@RequestMapping("/files")
public class FileUploadController {
// 指定文件保存的根目录
private final String UPLOAD_DIR = "D:\\tmp\\img\\";
/**
* 文件上传接口
* @param file 前端传入的文件对象
* @return 返回存在服务器的文件名称
*/
@PostMapping("/upload")
public String fileUpload(@RequestParam("file") MultipartFile file){
// 获取上传的图片文件后缀名
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf('.'));
// 检查文件是否为空
if (file.isEmpty()) {
return "File is empty!";
}
// 检查文件名是否合法,避免目录遍历攻击
String fileName = StringUtils.cleanPath(originalFilename);
if (fileName.contains("..")) {
return "Illegal name!";
}
try {
// 给上传的图片随机生成一个名称,将之返回,
// 用户就可以根据此名称下载图片,防止图片名称冲突。
UUID uuid = UUID.randomUUID();
String randomUUIDString = uuid.toString();
// 将文件保存到指定目录文件
File targetFile = new File(UPLOAD_DIR + randomUUIDString + fileExtension);
// 将传入的图片转存到指定目录文件
file.transferTo(targetFile);
// 返回随机名称(带有后缀名)
return randomUUIDString + fileExtension;
} catch (IOException e) {
e.printStackTrace();
return "server error!";
}
}
代码说明
具体步骤详解在代码注释中说明,其中重点就是动态获取UUID和传入文件的后缀名进行拼接,拼接后作为转存文件名,将传入文件转存在本地服务器目录文件中。并将拼接后的文件名返回给前端,以后,前端就可以根据此随机名来向服务器请求文件,防止了上传文件名时冲突的问题。
后端编写-文件下载接口
为前端提供文件(图片)下载接口,当前端请求此接口时,携带路径参数(文件名)访问接口,接口将向前端响应
// 指定文件下载时服务器存储文件的根目录
private final String DOWNLOAD_DIR = "D:\\tmp\\img\\";
/**
* 文件下载
*
* @param fileName 文件名,其中的.+为正则表达式,表示可以匹配任意后缀的文件
* @param response 响应
*/
@GetMapping("/download/{fileName:.+}")
public void fileDownLoad(@PathVariable String fileName, HttpServletResponse response) {
// 检查文件名是否合法,防止目录遍历攻击
if (fileName.contains("..")) {
// 文件名不合法,响应给前端状态400,表示错误请求
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
try {
// 获取输入流读取本地文件
FileInputStream fileInputStream = new FileInputStream(new File(DOWNLOAD_DIR + fileName));
// 获取输出流,用于向前端输出文件图片
ServletOutputStream outputStream = response.getOutputStream();
// 指定响应内容类型
response.setContentType("image/jpg");
// 遍历流输出操作
int len = 0;
byte[] bytes = new byte[1024];// 使用一个字节数组存容量为1024作为一组数据
// 循环读取文件中数据,当读取到的数据不等于-1时代表依然有数据
while((len = fileInputStream.read(bytes)) != -1){
// 将存储在bytes数组中的数据写出
outputStream.write(bytes, 0, len);
// 手动刷洗输出流,不必等到存满在输出
outputStream.flush();
}
// 关闭流资源
fileInputStream.close();
outputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}