欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

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

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

<?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 实现文件上传下载功能,希望对大家有所帮助

上一篇:

下一篇: