最近同事问我有没有有关于技术的电子书,我打开电脑上的小书库,但是邮件发给他太大了,公司又禁止用文件夹共享,于是花半天时间写了个小的文件上传程序,部署在自己的linux机器上。
提供功能: 1 .文件上传 2.文件列表展示以及下载
原有的上传那块很丑,写了点js代码优化了下,最后界面显示如下图:
先给出成果,下面就一步步演示怎么实现。
1.新建项目
首先当然是新建一个spring-boot工程,你可以选择在网站初始化一个项目或者使用ide的spring initialier功能,都可以新建一个项目。这里我从idea新建项目:
下一步,然后输入group和artifact,继续点击next:
这时候出现这个模块选择界面,点击web选项,勾上web,证明这是一个webapp,再点击template engines选择前端的模板引擎,我们选择thymleaf,spring-boot官方也推荐使用这个模板来替代jsp。
最后一步,然后等待项目初始化成功。
2.pom设置
首先检查项目需要添加哪些依赖,直接贴出我的pom文件:
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
|
<?xml version= "1.0" encoding= "utf-8" ?>
<project xmlns= "http://maven.apache.org/pom/4.0.0" xmlns:xsi= "http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation= "http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelversion> 4.0 . 0 </modelversion>
<groupid>com.shuqing28</groupid>
<artifactid>upload</artifactid>
<version> 0.0 . 1 -snapshot</version>
<packaging>jar</packaging>
<name>upload</name>
<description>demo project for spring boot</description>
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version> 1.5 . 9 .release</version>
<relativepath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceencoding>utf- 8 </project.build.sourceencoding>
<project.reporting.outputencoding>utf- 8 </project.reporting.outputencoding>
<java.version> 1.8 </java.version>
</properties>
<dependencies>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-thymeleaf</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-configuration-processor</artifactid>
<optional> true </optional>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-test</artifactid>
<scope>test</scope>
</dependency>
<!-- https: //mvnrepository.com/artifact/org.webjars/bootstrap -->
<dependency>
<groupid>org.webjars</groupid>
<artifactid>bootstrap</artifactid>
<version> 3.3 . 5 </version>
</dependency>
<!-- https: //mvnrepository.com/artifact/org.webjars.bower/jquery -->
<dependency>
<groupid>org.webjars.bower</groupid>
<artifactid>jquery</artifactid>
<version> 2.2 . 4 </version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-maven-plugin</artifactid>
</plugin>
</plugins>
</build>
</project>
|
可以查看到 spring-boot-starter-thymeleaf 包含了webapp,最后两个webjars整合了bootstrap和jquery,其它的等代码里用到再说。
最后一个spring boot maven plugin是系统创建时就添加的,它有以下好处:
1 . 它能够打包classpath下的所有jar,构建成一个可执行的“über-jar”,方便用户转移服务
2 . 自动搜索 public static void main() 方法并且标记为可执行类
3 . 根据spring-boot版本,提供内建的依赖解释。
3. 上传文件控制器
如果你只是使用springmvc上传文件,是需要配置一个 multipartresolver 的bean的,或者在 web.xml 里配置一个 <multipart-config> ,不过借助于spring-boot的自动配置,你什么都不必做。直接写控制器类,我们在 src/main/java 下新建controller的package,并且新建fileuploadcontroller:
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
|
package com.shuqing28.upload.controller;
import com.shuqing28.uploadfiles.pojo.linker;
import com.shuqing28.uploadfiles.exceptions.storagefilenotfoundexception;
import com.shuqing28.uploadfiles.service.storageservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.io.resource;
import org.springframework.http.httpheaders;
import org.springframework.http.responseentity;
import org.springframework.stereotype.controller;
import org.springframework.ui.model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.multipartfile;
import org.springframework.web.servlet.mvc.method.annotation.mvcuricomponentsbuilder;
import org.springframework.web.servlet.mvc.support.redirectattributes;
import java.io.ioexception;
import java.util.list;
import java.util.stream.collectors;
@controller
public class fileuploadcontroller {
private final storageservice storageservice;
@autowired
public fileuploadcontroller(storageservice storageservice) {
this .storageservice = storageservice;
}
@getmapping ( "/" )
public string listuploadedfiles(model model) throws ioexception {
list<linker> linkers = storageservice.loadall().map(
path -> new linker(mvcuricomponentsbuilder.frommethodname(fileuploadcontroller. class ,
"servefile" , path.getfilename().tostring()).build().tostring(),
path.getfilename().tostring())
).collect(collectors.tolist());
model.addattribute( "linkers" , linkers);
return "uploadform" ;
}
@getmapping ( "/files/{filename:.+}" )
@responsebody
public responseentity<resource> servefile( @pathvariable string filename) {
resource file = storageservice.loadasresource(filename);
return responseentity.ok().header(httpheaders.content_disposition,
"attachment; filename=\"" + file.getfilename() + "\"" ).body(file);
}
@postmapping ( "/" )
public string handlefileupload( @requestparam ( "file" ) multipartfile file,
redirectattributes redirectattributes) {
storageservice.store(file);
redirectattributes.addflashattribute( "message" ,
"you successfully uploaded " + file.getoriginalfilename() + "!" );
return "redirect:/" ;
}
@exceptionhandler (storagefilenotfoundexception. class )
public responseentity<?> handlestoragefilenotfound(storagefilenotfoundexception exc) {
return responseentity.notfound().build();
}
}
|
类定义处添加了 @controller 注解,证明这是一个controller,每个方法前添加了 @getmapping 和 @postmapping 分别相应get和post请求。
首先是 @getmapping("/") ,方法 listuploadedfiles ,顾名思义,显示文件列表,这里我们借助于storageservice遍历文件夹下的所有文件,并且用map方法提合成了链接和文件名列表,返回了一个linker对象的数组,linker对象是一个简单pojo,只包含下面两部分:
1
2
|
private string fileurl;
private string filename;
|
这个方法包含了对java8中stream的使用,如果有不理解的可以看看这篇文章 java8 特性详解(二) stream api .
接下来是 @getmapping("/files/{filename:.+}")
,方法是 servefile ,该方法提供文件下载功能,还是借助于storageservice,后面会贴出storageservice的代码。最后使用responseentity,把文件作为body返回给请求方。
@postmapping("/")
的 handlefileupload 使用post请求来上传文件,参数 @requestparam("file") 提取网页请求里的文件对象,还是使用storageservice来保存对象,最后使用重定向来刷新网页,并且给出成功上传的message。
4. 文件处理
上面controller调用的很多方法由storageservice提供,我们定义一个接口,包含以下方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package com.shuqing28.uploadfiles.service;
import org.springframework.core.io.resource;
import org.springframework.web.multipart.multipartfile;
import java.nio.file.path;
import java.util.stream.stream;
public interface storageservice {
void init();
void store(multipartfile file);
stream<path> loadall();
path load(string filename);
resource loadasresource(string filename);
void deleteall();
}
|
因为我这里只是借助于本地文件系统处理文件的长传下载,所以有了以下实现类:
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
|
package com.shuqing28.uploadfiles.service;
import com.shuqing28.uploadfiles.exceptions.storageexception;
import com.shuqing28.uploadfiles.exceptions.storagefilenotfoundexception;
import com.shuqing28.uploadfiles.config.storageproperties;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.core.io.resource;
import org.springframework.core.io.urlresource;
import org.springframework.stereotype.service;
import org.springframework.util.filesystemutils;
import org.springframework.util.stringutils;
import org.springframework.web.multipart.multipartfile;
import java.io.ioexception;
import java.net.malformedurlexception;
import java.nio.file.files;
import java.nio.file.path;
import java.nio.file.paths;
import java.nio.file.standardcopyoption;
import java.util.stream.stream;
@service
public class filesystemstorageservice implements storageservice {
private final path rootlocation;
@autowired
public filesystemstorageservice(storageproperties properties) {
this .rootlocation = paths.get(properties.getlocation());
}
@override
public void init() {
try {
files.createdirectories(rootlocation);
}
catch (ioexception e) {
throw new storageexception( "could not initialize storage" , e);
}
}
@override
public void store(multipartfile file) {
string filename = stringutils.cleanpath(file.getoriginalfilename());
try {
if (file.isempty()) {
throw new storageexception( "failed to store empty file" + filename);
}
if (filename.contains( ".." )) {
// this is a security check
throw new storageexception(
"cannot store file with relative path outside current directory "
+ filename);
}
files.copy(file.getinputstream(), this .rootlocation.resolve(filename), standardcopyoption.replace_existing);
} catch (ioexception e) {
throw new storageexception( "failed to store file" + filename, e);
}
}
@override
public stream<path> loadall() {
try {
return files.walk( this .rootlocation, 1 )
.filter(path -> !path.equals( this .rootlocation))
.map(path-> this .rootlocation.relativize(path));
}
catch (ioexception e) {
throw new storageexception( "failed to read stored files" , e);
}
}
@override
public path load(string filename) {
return rootlocation.resolve(filename);
}
@override
public resource loadasresource(string filename) {
try {
path file = load(filename);
resource resource = new urlresource(file.touri());
if (resource.exists() || resource.isreadable()) {
return resource;
}
else {
throw new storagefilenotfoundexception(
"could not read file: " + filename);
}
}
catch (malformedurlexception e) {
throw new storagefilenotfoundexception( "could not read file: " + filename, e);
}
}
@override
public void deleteall() {
filesystemutils.deleterecursively(rootlocation.tofile());
}
}
|
这个类也基本运用了java的nio,使用path对象定义了location用于文件的默认保存路径。
先看 store 方法,store接受一个multipartfile对象作为参数,想比于传统jsp中只是传二进制字节数组,multipartfile提供了很多方便调用的方法让我们可以获取到上传文件的各项信息:
1
2
3
4
5
6
7
8
9
10
|
public interface multipartfile extends inputstreamsource {
string getname();
string getoriginalfilename();
string getcontenttype();
boolean isempty();
long getsize();
byte [] getbytes() throws ioexception;
inputstream getinputstream() throws ioexception;
void transferto(file dest) throws ioexception, illegalstateexception;
}
|
代码里使用了files的copy方法把文件流拷到location对应的path里,当然我们也可以使用transferto方法保存文件, file.transferto(this.rootlocation.resolve(filename).tofile());
loadall方法加载该路径下的所有文件path信息, loadasresource 则是加载文件为一个resource对象,再看controller的代码,最后是接受一个resource对象作为body返回给请求方。
5. 前端模板
最后定义了前端模板,这里依旧先看代码:
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
|
<html xmlns:th= "http://www.thymeleaf.org" >
<head>
<title>share files</title>
</head>
<body>
<div class = "col-md-8 col-md-offset-2" th: if = "${message}" >
<h2 th:text= "${message}" />
</div>
<div class = "col-md-8 col-md-offset-2" >
<form method= "post" action= "/" enctype= "multipart/form-data" >
<!-- component start -->
<input type= "file" name= "file" class = "input-ghost" style= "visibility:hidden; height:0" />
<div class = "form-group" >
<div class = "input-group input-file" name= "fichier1" >
<input type= "text" class = "form-control" placeholder= 'choose a file...' />
<span class = "input-group-btn" >
<button class = "btn btn-default btn-choose" type= "button" >choose</button>
</span>
</div>
</div>
<!-- component end -->
<div class = "form-group" >
<button type= "submit" class = "btn btn-primary pull-right" >submit</button>
<button type= "reset" class = "btn btn-danger" >reset</button>
</div>
</form>
</div>
<div class = "col-md-8 col-md-offset-2" >
<ul>
<li th:each= "linker: ${linkers}" >
<a th:href= "${linker.fileurl}" rel= "external nofollow" th:text= "${linker.filename}" />
</li>
</ul>
</div>
<script src= "//ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.min.js" ></script>
<script src= "/webjars/bootstrap/3.3.5/js/bootstrap.min.js" ></script>
<script type= "text/javascript" th:inline= "javascript" >
function bs_input_file() {
$( ".input-file" ).before(
function() {
if ( ! $( this ).prev().hasclass( 'input-ghost' ) ) {
var element = $( ".input-ghost" );
element.change(function(){
element.next(element).find( 'input' ).val((element.val()).split( '\\' ).pop());
});
$( this ).find( "button.btn-choose" ).click(function(){
element.click();
});
$( this ).find( "button.btn-reset" ).click(function(){
element.val( null );
$( this ).parents( ".input-file" ).find( 'input' ).val( '' );
});
$( this ).find( 'input' ).css( "cursor" , "pointer" );
$( this ).find( 'input' ).mousedown(function() {
$( this ).parents( '.input-file' ).prev().click();
return false ;
});
return element;
}
}
);
}
$(function() {
bs_input_file();
});
</script>
<link rel= "stylesheet" href= "/webjars/bootstrap/3.3.5/css/bootstrap.min.css" rel= "external nofollow" />
</body>
</html>
|
这里重要的地方还是 <form> 标签内的内容, <form method="post" action="/" enctype="multipart/form-data"> enctype 一定要写成 multipart/form-data ,使用post上传文件,原有的上传控件很丑,所以做了一个text+input放在表面,在下面放了一个隐形的上传文件的input,可以自己看看代码,本文就不啰嗦了。
下面还放了一个list用于展示文件列表,这里我们获取到服务端提供的linkers对象,不断foreach就可以获得里面的两个元素fileurl和filename。
这里jquery换成了微软的cdn,webjars的总是引入不进来,不知道什么原因。
其它设置
在 src/main/resources/application.properties 里设置上传文件大小限制
1
2
|
spring.http.multipart.max-file-size=128mb
spring.http.multipart.max-request-size=128mb
|
另外在``还设置了文件默认保存路径:
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.shuqing28.uploadfiles.config;
import org.springframework.boot.context.properties.configurationproperties;
@configurationproperties ( "storage" )
public class storageproperties {
private string location = "/home/jenkins/upload-files/" ;
public string getlocation() {
return location;
}
public void setlocation(string location) {
this .location = location;
}
}
|
这里注意,由于storageproperties的设置,在application的那个类中要添加上
1
2
3
4
5
6
7
8
|
@enableconfigurationproperties 注解
@springbootapplication
@enableconfigurationproperties (storageproperties. class )
public class uploadapplication {
public static void main(string[] args) {
springapplication.run(uploadapplication. class , args);
}
}
|
总结
以上所述是小编给大家介绍的spring boot + thymeleaf 实现文件上传下载功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:https://juejin.im/post/5a326dcaf265da431048685e