Spring Boot + thymeleaf 实现文件上传下载功能
最近同事问我有没有有关于技术的电子书,我打开电脑上的小书库,但是邮件发给他太大了,公司又禁止用文件夹共享,于是花半天时间写了个小的文件上传程序,部署在自己的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文件:
<?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:
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,只包含下面两部分:
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提供,我们定义一个接口,包含以下方法:
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(); }
因为我这里只是借助于本地文件系统处理文件的长传下载,所以有了以下实现类:
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提供了很多方便调用的方法让我们可以获取到上传文件的各项信息:
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. 前端模板
最后定义了前端模板,这里依旧先看代码:
<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 里设置上传文件大小限制
spring.http.multipart.max-file-size=128mb spring.http.multipart.max-request-size=128mb
另外在``还设置了文件默认保存路径:
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的那个类中要添加上
@enableconfigurationproperties注解 @springbootapplication @enableconfigurationproperties(storageproperties.class) public class uploadapplication { public static void main(string[] args) { springapplication.run(uploadapplication.class, args); } }
总结
以上所述是小编给大家介绍的spring boot + thymeleaf 实现文件上传下载功能,希望对大家有所帮助
推荐阅读
-
Spring Boot + thymeleaf 实现文件上传下载功能
-
spring boot实现验证码功能
-
Spring Cloud中FeignClient实现文件上传功能
-
HTML5拖拽文件到浏览器并实现文件上传下载功能代码
-
C# 文件上传下载(Excel导入,多线程下载)功能的实现代码
-
Spring boot搭建web应用集成thymeleaf模板实现登陆
-
spring boot+thymeleaf+bootstrap实现后台管理系统界面
-
使用 Spring Boot 2.0 + WebFlux 实现 RESTful API功能
-
Spring boot + LayIM + t-io 实现文件上传、 监听用户状态的实例代码
-
vue-cli+axios实现文件上传下载功能(下载接收后台返回文件流)