服务器之家

服务器之家 > 正文

Spring Boot + thymeleaf 实现文件上传下载功能

时间:2021-03-22 14:01     来源/作者:卡巴拉的树

最近同事问我有没有有关于技术的电子书,我打开电脑上的小书库,但是邮件发给他太大了,公司又禁止用文件夹共享,于是花半天时间写了个小的文件上传程序,部署在自己的linux机器上。

提供功能: 1 .文件上传 2.文件列表展示以及下载

原有的上传那块很丑,写了点js代码优化了下,最后界面显示如下图:

Spring Boot + thymeleaf 实现文件上传下载功能

先给出成果,下面就一步步演示怎么实现。

1.新建项目

首先当然是新建一个spring-boot工程,你可以选择在网站初始化一个项目或者使用ide的spring initialier功能,都可以新建一个项目。这里我从idea新建项目:

Spring Boot + thymeleaf 实现文件上传下载功能

下一步,然后输入group和artifact,继续点击next:

Spring Boot + thymeleaf 实现文件上传下载功能

这时候出现这个模块选择界面,点击web选项,勾上web,证明这是一个webapp,再点击template engines选择前端的模板引擎,我们选择thymleaf,spring-boot官方也推荐使用这个模板来替代jsp。

Spring Boot + thymeleaf 实现文件上传下载功能

 
Spring Boot + thymeleaf 实现文件上传下载功能

 

最后一步,然后等待项目初始化成功。

Spring Boot + thymeleaf 实现文件上传下载功能

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

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
返回顶部