经常在项目中需要使用上传文件功能,找了不少前端上传组件,都不是很好用,今天尝试了一下DropZone,发现不错,顺便记录一下使用过程,方便后续查阅。在做开发的时候,经常需要调研一些技术,因此前后端都需要用到,为方便开发,这里采用传统的开发方式,没有做前后端分离,方便调试。前端采用HTML+Bootstrap+jQuery,后端采用SpringBoot2.6.3。
总体
新建一个SpringBoot程序,目录结构如下:
files:存放程序运行过程中的生成文件
logs:日志目录
src:源代码目录
uploads:上传文件目录
前端第三方文件放在/src/main/resources/static目录下,主要有bootstrap、dropzone和jquery,模版文件放在/src/main/resources/templates目录下,在这里注意调试时,经常需要修改HTML页面,需要快速将HTML页面编译到到运行目录/target/classes,这里使用快捷键CTRL+F9即可,前提是IDEA的自动构建项目要勾上
前端
首先从DropZone下载相关的js文件和css文件,或者直接让一些大模型给一个示例,我这里采用腾讯元宝,稍微修改一下,基本可以用,但是不少地方还是需要自己定制。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Upload</title>
<link rel="stylesheet" href="./bootstrap/css/bootstrap.min.css">
<script src="./bootstrap/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="./dropzone/dropzone.min.css">
<script src="./dropzone/dropzone.min.js"></script>
<script src="./jquery/jquery.min.js"></script>
<style>
.dropzone { padding: 3px 0 0 0; margin: 0 0 8px 0; text-align: center; overflow: hidden; width: 120px;
min-height: 30px; height: 36px; border: #ccc solid 1px; }
.dz-processing, .dz-button { display: none; }
#fileList { display: flex; flex-wrap: wrap; gap: 10px; }
.file-item { background-color: #f9f9f9; padding: 5px; border: 1px solid #ccc; border-radius: 0; cursor: pointer; }
</style>
</head>
<body>
<div class="container">
<div style="padding:10px 0;">参看:https://www.dropzone.dev/</div>
<div class="dropzone primary" id="my-dropzone">上传文件</div>
<div id="message"></div>
<div id="fileList"></div>
</div>
<script>
Dropzone.options.myDropzone = {
url: '/upload',
paramName: 'file',
maxFiles:10,//一次性上传的文件数量上限
maxFilesize: 20, //MB
acceptedFiles: ".jpg,.gif,.png", //上传的类型
parallelUploads: 3,
dictMaxFilesExceeded: "您最多只能上传10个文件!",
dictResponseError: '文件上传失败!',
dictInvalidFileType: "你不能上传该类型文件,文件类型只能是*.jpg,*.gif,*.png。",
dictFallbackMessage:"浏览器不受支持",
dictFileTooBig:"文件过大上传文件最大支持.",
previewTemplate: '<div></div>',
showPreviewOnDrop: false,
showPreviewOnUpload: false,
init: function() {
this.on('success', function(file, response) {
$('#message').html('<p class="text-success">' + response.msg + '</p>');
let json = JSON.parse(file.xhr.response);
let fileName = '<div class="file-item" οnclick="showImage(\'' + json.data + '\')">' + json.data + '</div>';
$('#fileList').prepend(fileName);
});
this.on('error', function(file, response) {
$('#message').html('<p class="text-danger">Failed to upload file.</p>');
});
// 自定义文件显示方式
this.on('addedfile', function(file) {
console.log(file);
});
this.on('removedfile', function(file) {
console.log(file);
$('#fileList .file-item[data-dz-id="' + file.id + '"]').remove();
});
}
};
function showImage(url) {
window.open('image/' + url, '_blank')
}
$(document).ready(function() {
$.get('/images', function(res) {
let array = res.data;
let count = array.length;
for (let i = 0; i < count; i ++) {
let item = '<div class="file-item" οnclick="showImage(\'' + array[i] + '\')">' + array[i] + '</div>';
$('#fileList').append(item);
}
})
});
</script>
</body>
</html>
后端
后端主要实现三个接口,上传接口、获取图片列表接口、显示单个图片接口,分别对应/upload,/images,/image/{filename}
package org.example.imgtool.controller;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.example.imgtool.ImgtoolApplication;
import org.example.imgtool.utils.FileUtil;
import org.example.imgtool.utils.PathUtil;
import org.example.imgtool.utils.SnowflakeGenerator;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
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.Collections;
import java.util.List;
@Slf4j
@RestController
public class FileUploadController {
private static final String UPLOAD_DIR = "uploads/";
@GetMapping("/images")
public ResponseEntity<JSONObject> getImages() {
String imagesPath = PathUtil.getAppPath(ImgtoolApplication.class) + "files/images.txt";
List<String> list = FileUtil.readFileToList(imagesPath);
JSONObject json = new JSONObject(true);
json.put("code", HttpStatus.OK.value());
json.put("data", list);
json.put("msg", "获取数据成功");
return new ResponseEntity<>(json, HttpStatus.OK);
}
@GetMapping(value = "/image/{filename}", produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> getRemoteImage(@PathVariable String filename) throws IOException {
String imagePath = PathUtil.getAppPath(ImgtoolApplication.class) + "uploads/" + filename;
byte[] bytes = FileUtil.getFileByteArray(imagePath);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.IMAGE_JPEG));
File file = new File(imagePath);
if (file.exists()) {
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
@PostMapping("/upload")
public ResponseEntity<JSONObject> uploadFile(@RequestParam("file") MultipartFile file) {
try {
// 检查上传目录是否存在,如果不存在则创建
Path uploadPath = Paths.get(UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 保存文件到上传目录
String fileName = file.getOriginalFilename();
int index = fileName.lastIndexOf(".");
String extension = fileName.substring(index);
String newFileName = String.format("%d%s", SnowflakeGenerator.generatorId(), extension);
log.debug(newFileName);
Path filePath = uploadPath.resolve(newFileName);
Files.copy(file.getInputStream(), filePath);
// 文件名写入文件
String imagesPath = PathUtil.getAppPath(ImgtoolApplication.class) + "files/images.txt";
File f = new File(imagesPath);
List<String> list = null;
if (f.exists()) {
list = FileUtil.readFileToList(imagesPath);
} else {
list = Collections.emptyList();
}
list.add(0, newFileName);
FileUtil.writeFile(imagesPath, list);
JSONObject json = new JSONObject(true);
json.put("code", HttpStatus.OK.value());
json.put("data", newFileName);
json.put("msg", "上传成功" + newFileName);
return new ResponseEntity<>(json, HttpStatus.OK);
} catch (IOException e) {
log.error("Upload image fail : {}", e.getMessage());
JSONObject json = new JSONObject(true);
json.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
json.put("msg", "上传失败");
return new ResponseEntity<>(json, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
配置文件如下,注意这里spring.thymeleaf.cache一定要配置为false,这样前面提到的CTRL+F9就可以实时调试,方便前端调试代码。
spring.thymeleaf.cache=false
spring.thymeleaf.check-template=true
spring.thymeleaf.check-template-location=true
spring.thymeleaf.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.excluded-view-names=
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
server.port=8080
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
效果
最后实现效果如下
源代码
转到https://download.csdn.net/download/Angushine/89672489下载即可。