原因
近日,自己的服务器每次上传文件都感觉比较麻烦,所以想着自己动手搞一个文件上传和下载的服务.
实现的功能
- 大文件分片上传
- 文件下载
- 显示目录
- 新建文件夹
项目前的准备
- 技术采用了uploader 和spring的技术
- uploader 相关包的下载 如下:
- maven 相关依赖
<build>
<!--声明并引入子项目共有的插件-->
<!-- <pluginManagement>-->
<plugins>
<!--java maven的打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.3.RELEASE</version>
<configuration>
<mainClass>com.xiaobo.Star</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version> <!-- or newer version -->
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<!--指定入口文件的位置-->
<mainClass>com.xiaobo.Star</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>
jar-with-dependencies
</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
</dependencies>
开始飙代码
- 前端html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webuploader</title>
</head>
<!--引入CSS-->
<link rel="stylesheet" type="text/css" href="webuploader.css">
<script src="jquery-1.11.1.js"></script>
<script src="webuploader.js"></script>
<style>
#upload-container, #upload-list {
width: 500px;
margin: 0 auto;
}
#upload-container {
cursor: pointer;
border-radius: 15px;
background: #EEEFFF;
height: 200px;
}
#upload-list {
height: 800px;
border: 1px solid #EEE;
border-radius: 5px;
margin-top: 10px;
padding: 10px 20px;
}
#upload-container > span {
widows: 100%;
text-align: center;
color: gray;
display: block;
padding-top: 15%;
}
.upload-item {
margin-top: 5px;
padding-bottom: 5px;
border-bottom: 1px dashed gray;
}
.percentage {
height: 5px;
background: green;
}
.btn-delete, .btn-retry {
cursor: pointer;
color: gray;
}
.btn-delete:hover {
color: orange;
}
.btn-retry:hover {
color: green;
}
.fileImage {
background-image: url("images/file.png");
background-repeat: no-repeat;
background-size: 25px 25px;
height: 25px;
width: 400px;
cursor: pointer;
overflow: hidden visible;
}
.fileFont {
margin-left: 30px;
margin-top: 50px;
overflow: hidden;
weight: auto;
word-break: normal;
white-space: pre-wrap;
word-wrap: break-word;
overflow: hidden;
}
.directoryImage {
background-image: url("images/directory.png");
background-repeat: no-repeat;
background-size: 25px 25px;
height: 25px;
width: 400px;
cursor: pointer;
overflow: hidden visible;
}
.directoryFont {
margin-left: 30px;
margin-top: 50px
}
.upperLevel {
height: 40px;
cursor: pointer;
float: left;
}
.createFile {
height: 40px;
cursor: pointer;
float: right;
}
.warnMessage{
position: absolute;
left: 42%;
top: 20px;
color: #00FF00;font-family: 'Georgia,Serif';
font-style: oblique;
display: none;
}
</style>
<! --引入JS-->
<body>
<div style="clear: both;width: 80%;height:10px;margin:0 auto">
<div style="float: left;width: 40%">
<div id="upload-container"
">
<span>点击或将文件拖拽至此上传</span>
</div>
<div id="upload-list">
</div>
</div>
<div style="width: 40%;float: right;padding: 100px;">
<div style="width: 400px;height: 40px;clear: both">
<div class="upperLevel">上一级</div>
<div class="createFile">新建文件夹</div>
</div>
<div id="filePath">
</div>
</div>
</div>
<button id="picker" style="display: none;">点击上传文件</button>
<div class="warnMessage">
<div>提示信息</div>
</div>
</body>
<script>
var absolutely = '/'
getPath()
function getPath(){
$.ajax({
type : "GET",
url : 'http://localhost:8080/getFilePath?path='+absolutely,
success : function(message){
console.log(message);
if (message.code == 200){
listFile(message)
showWarnMessage('获取列表成功')
}
}
});
}
function showWarnMessage(message){
var warnMessage = $(".warnMessage");
warnMessage.html("<div style='display: block'}>"+message+"<div>")
warnMessage.slideDown(2000);
setTimeout(function(){warnMessage.slideUp(2000);},1000);
}
function createFile(filename){
$.ajax({
type : "GET",
url : 'http://localhost:8080/createFile?path='+absolutely+filename,
success : function(message){
// console.log(message);
if (message.code == 200){
showWarnMessage('创建文件成功')
}
}
});
}
function download(filename){
var url = 'http://localhost:8080/download'
var form = $("<form></form>").attr("action", url).attr("method", "get");
form.append($("<input></input>").attr("type", "hidden").attr("name", "path").attr("value", absolutely+filename));
form.appendTo('body').submit().remove();
}
function listFile(message){
var file = '<div id="listFile">'
for (let filePath of message.data.files){
file += '<div class="fileImage"> <span class="fileFont">'+filePath+'</span></div>'
}
for (let filePath of message.data.directory){
file += '<div class="directoryImage"> <span class="directoryFont">'+filePath+'</span></div>'
}
file +="</div>"
$('#filePath').html(file)
}
$('body').on('click','#filePath .directoryImage',function (evnet){
let text = $(this).text().trim()
absolutely +=text +'/'
console.log(absolutely)
var name = absolutely
getPath()
})
$('body').on('click','#filePath .fileImage',function (evnet){
let name = $(this).text()
console.log(this.innerHTML)
// let text = this.text();
var r = confirm("确定下载文件: "+ name)
if (r == true) {
console.log("开始下载")
download(name.trim())
}
})
$('.upperLevel').click(function(event) {
var name = absolutely.substring(absolutely.lastIndexOf('/'))
absolutely = name
getPath()
});
$('.createFile').click(function(event) {
var txt;
var fileName = prompt("请输入文件夹的名称:", "11");
if (fileName != null || fileName.trim() != "") {
createFile(fileName)
getPath()
}
});
$('#upload-container').click(function(event) {
$("#picker").find('input').click();
});
var uploader = WebUploader.create({
auto: true,// 选完文件后,是否自动上传。
swf: 'Uploader.swf',// swf文件路径
server: 'http://localhost:8080/upload',// 文件接收服务端。
dnd: '#upload-container',
pick: '#picker',// 内部根据当前运行是创建,可能是input元素,也可能是flash. 这里是div的id
multiple: true, // 选择多个
chunked: true,// 开启分片上传。
threads: 20, // 上传并发数。允许同时最大上传进程数。
method: 'POST', // 文件上传方式,POST或者GET。
fileSizeLimit: 1024*1024*1024*10*1024, //验证文件总大小是否超出限制, 超出则不允许加入队列。
fileSingleSizeLimit: 1024*1024*1024, //验证单个文件大小是否超出限制, 超出则不允许加入队列。
fileVal:'upload', // [默认值:'file'] 设置文件上传域的name。
formData: { "path": absolutely}
});
uploader.on("beforeFileQueued", function(file,data) {
console.log(file); // 获取文件的后缀
});
uploader.on( 'uploadBeforeSend', function( block, data ) {
// block为分块数据。
// file为分块对应的file对象。
var file = block.file;
// 修改data可以控制发送哪些携带数据。
data.path = absolutely;
});
uploader.on('fileQueued', function(file) {
// 选中文件时要做的事情,比如在页面中显示选中的文件并添加到文件列表,获取文件的大小,文件类型等
console.log(file.ext); // 获取文件的后缀
console.log(file.size);// 获取文件的大小
console.log(file.name);
var html = '<div class="upload-item"><span>文件名:'+file.name+'</span><span data-file_id="'+file.id+'" class="btn-delete">删除</span><span data-file_id="'+file.id+'" class="btn-retry">重试</span><div class="percentage '+file.id+'" style="width: 0%;"></div></div>';
$('#upload-list').append(html);
uploader.md5File( file )//大文件秒传
// 及时显示进度
.progress(function(percentage) {
console.log('Percentage:', percentage);
})
// 完成
.then(function(val) {
console.log('md5 result:', val);
});
});
uploader.on('uploadProgress', function(file, percentage) {
console.log(percentage * 100 + '%');
var width = $('.upload-item').width();
$('.'+file.id).width(width*percentage);
});
uploader.on('uploadSuccess', function(file, response) {
console.log(file.id+"传输成功");
getPath()
// showWarnMessage('上传文件成功')
});
uploader.on('uploadError', function(file) {
console.log(file);
console.log(file.id+'upload error')
showWarnMessage('上传文件失败')
});
$('#upload-list').on('click', '.upload-item .btn-delete', function() {
// 从文件队列中删除某个文件id
file_id = $(this).data('file_id');
// uploader.removeFile(file_id); // 标记文件状态为已取消
uploader.removeFile(file_id, true); // 从queue中删除
console.log(uploader.getFiles());
});
$('#upload-list').on('click', '.btn-retry', function() {
uploader.retry($(this).data('file_id'));
});
uploader.on('uploadComplete', function(file) {
console.log(uploader.getFiles());
});
</script>
</html>
- 后端上传代码
@RequestMapping("/upload")
@ResponseBody
@CrossOrigin
public ApiResponse upload(HttpServletRequest request, HttpServletResponse response, @RequestParam MultipartFile[] myfiles) {
return fileService.upload1(request, response);
}
public ApiResponse upload1(HttpServletRequest request, HttpServletResponse response) {
if (ServletFileUpload.isMultipartContent(request)) {
String chunks = request.getParameter("chunks");
MultipartFile uploadFile = ((MultipartHttpServletRequest) request).getFileMap().get("upload");
// byte 数组
byte[] bytes = new byte[1024];
// 获取文件名
String name = request.getParameter("name");
String path = request.getParameter("path");
// 判断是否是大文件开启 分片上传
if (chunks == null) {
log.warn(" 文件: " + name + "开始上传");
System.out.println(" 文件: " + name + "开始上传");
InputStream inputStream = null;
File file = null;
BufferedOutputStream bw = null;
try {
inputStream = uploadFile.getInputStream();
file = new File(filePath + path + "\\" + name);
System.out.println(filePath + path + "\\" + name);
if (!file.exists()) {
bw = new BufferedOutputStream(new FileOutputStream(file));
file.createNewFile();
int temp = 0;
while ((temp = inputStream.read(bytes)) != -1) {
bw.write(bytes, 0, temp);
bw.flush();
}
bw.flush();
log.warn(filePath + path + "\\" + name + ": 文件上传成功");
System.out.println(filePath + path + "\\" + name + ": 文件上传成功");
} else {
// 存在抛出异常
response.getWriter().write("文件名已存在" + name);
response.reset();
log.error("文件名已存在" + name);
System.out.println("文件名已存在" + name);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
String chunk = request.getParameter("chunk");
InputStream inputStream = null;
File file = null;
BufferedOutputStream bw = null;
String[] fileNames = name.split("\\.");
StringBuilder fileName = new StringBuilder();
for (int i = 0; i < fileNames.length - 1; i++) {
fileName.append(fileNames[i]);
if (i < fileNames.length - 2) {
fileName.append(".");
}
}
String fileFuffix = fileNames[fileNames.length - 1];
String tempName = filetemp + "\\" + fileName;
try {
inputStream = uploadFile.getInputStream();
file = new File(tempName + "$%" + chunk);
log.warn(" 文件: " + tempName + "$%" + chunk + "开始上传");
System.out.println(" 文件: " + tempName + "$%" + chunk + "开始上传");
if (!file.exists()) {
bw = new BufferedOutputStream(new FileOutputStream(file));
file.createNewFile();
int temp = 0;
while ((temp = inputStream.read(bytes)) != -1) {
bw.write(bytes, 0, temp);
bw.flush();
}
bw.flush();
} else {
// 存在抛出异常
// log.warn("临时文件已存在" + tempName + "$%" + chunk);
System.out.println("临时文件已存在" + tempName + "$%" + chunk);
}
if (Integer.parseInt(chunks) - 1 == Integer.parseInt(chunk)) {
log.warn(filePath + "\\" + fileName + "." + fileFuffix + ": 开始合并");
System.out.println(filePath + "\\" + fileName + "." + fileFuffix + ": 开始合并");
if (mergeFile(filePath, chunks, path, fileName.toString(), fileFuffix).equals("合并失败")) {
log.error("上传文件: " + name + "失败");
System.out.println("上传文件: " + name + "失败");
return ApiResponse.FAIL("合并失败");
}
log.warn(filePath + name + ": 文件上传成功");
System.out.println(filePath + name + ": 文件上传成功");
}
} catch (Exception e) {
e.printStackTrace();
log.error("上传文件: " + name + "失败");
System.out.println("上传文件: " + name + "失败");
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
log.error("上传文件: " + name + "中 流关闭失败");
System.out.println("上传文件: " + name + "中 流关闭失败");
}
}
}
}
return ApiResponse.OK("上传成功");
}
- 后台下载接口
@RequestMapping("/download")
@ResponseBody
@CrossOrigin
public void download(HttpServletRequest request, HttpServletResponse response) {
fileService.download1(request, response);
}
public void download1(HttpServletRequest request, HttpServletResponse response) {
String path = request.getParameter("path");
response.setCharacterEncoding("utf-8");
InputStream inputStream = null;
File file = null;
BufferedOutputStream bw = null;
try {
file = new File(filePath + path);
log.warn("开始下载文件: " + filePath + path);
System.out.println("开始下载文件: " + filePath + path);
// 分片下载
long fSize = file.length();
// 应中文可能会出翔的乱码
String fileName = URLEncoder.encode(file.getName(), utf8);
response.setContentType("application/x-download");
// 让用户选择下载到的地址
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
// 查看客户端是否支持分片下载
response.addHeader("Accept-Ranges", "bytes");
response.addHeader("fSize", String.valueOf(fSize));
response.addHeader("fName", "IDEA.zip");
long pos = 0, last = fSize - 1, sum = 0;
long rangeLength = last - pos + 1;
String contentRange = new StringBuffer("bytes ")
.append(pos)
.append("-")
.append(last)
.append("/")
.append(fSize)
.toString();
response.setHeader("Content-Range", contentRange);
response.setHeader("Content-Length", String.valueOf(rangeLength));
bw = new BufferedOutputStream(response.getOutputStream());
inputStream = new BufferedInputStream(new FileInputStream(file));
byte[] bytes = new byte[1024];
int temp = 0;
while (sum < rangeLength) {
temp = inputStream.read(bytes);
sum += temp;
bw.write(bytes, 0, temp);
bw.flush();
}
log.warn("下载完成");
System.out.println("下载完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
后端获取文件列表和创建文件,删除文件
@RequestMapping("/getFilePath")
@ResponseBody
@CrossOrigin
public ApiResponse getFilePath(HttpServletRequest request, HttpServletResponse response) {
HashMap<String, Object> filePath = fileService.getFilePath(request, response);
return ApiResponse.OK(filePath);
}
@RequestMapping("/createFile")
@ResponseBody
@CrossOrigin
public ApiResponse createFile(HttpServletRequest request, HttpServletResponse response) {
return fileService.createFile(request, response);
}
@RequestMapping("/deleteFile")
@ResponseBody
@CrossOrigin
public ApiResponse deleteFile(HttpServletRequest request, HttpServletResponse response) throws InterruptedException, UnsupportedEncodingException {
return fileService.deleteFile(request, response);
}
项目到此顺利完成.