一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

2021-03-22 14:01卡巴拉的樹 Java教程

最近同事問(wèn)我有沒有有關(guān)于技術(shù)的電子書,我打開電腦上的小書庫(kù),但是郵件發(fā)給他太大了,公司又禁止用文件夾共享,于是花半天時(shí)間寫了個(gè)小的文件上傳程序,部署在自己的Linux機(jī)器上,需要的朋友可以參考下

最近同事問(wèn)我有沒有有關(guān)于技術(shù)的電子書,我打開電腦上的小書庫(kù),但是郵件發(fā)給他太大了,公司又禁止用文件夾共享,于是花半天時(shí)間寫了個(gè)小的文件上傳程序,部署在自己的linux機(jī)器上。

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

原有的上傳那塊很丑,寫了點(diǎn)js代碼優(yōu)化了下,最后界面顯示如下圖:

Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

先給出成果,下面就一步步演示怎么實(shí)現(xiàn)。

1.新建項(xiàng)目

首先當(dāng)然是新建一個(gè)spring-boot工程,你可以選擇在網(wǎng)站初始化一個(gè)項(xiàng)目或者使用ide的spring initialier功能,都可以新建一個(gè)項(xiàng)目。這里我從idea新建項(xiàng)目:

Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

下一步,然后輸入group和artifact,繼續(xù)點(diǎn)擊next:

Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

這時(shí)候出現(xiàn)這個(gè)模塊選擇界面,點(diǎn)擊web選項(xiàng),勾上web,證明這是一個(gè)webapp,再點(diǎn)擊template engines選擇前端的模板引擎,我們選擇thymleaf,spring-boot官方也推薦使用這個(gè)模板來(lái)替代jsp。

Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

 
Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

 

最后一步,然后等待項(xiàng)目初始化成功。

Spring Boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能

2.pom設(shè)置

首先檢查項(xiàng)目需要添加哪些依賴,直接貼出我的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,最后兩個(gè)webjars整合了bootstrap和jquery,其它的等代碼里用到再說(shuō)。

最后一個(gè)spring boot maven plugin是系統(tǒng)創(chuàng)建時(shí)就添加的,它有以下好處:

1 . 它能夠打包c(diǎn)lasspath下的所有jar,構(gòu)建成一個(gè)可執(zhí)行的“über-jar”,方便用戶轉(zhuǎn)移服務(wù)

2 . 自動(dòng)搜索 public static void main() 方法并且標(biāo)記為可執(zhí)行類

3 . 根據(jù)spring-boot版本,提供內(nèi)建的依賴解釋。

3. 上傳文件控制器

如果你只是使用springmvc上傳文件,是需要配置一個(gè) multipartresolver 的bean的,或者在 web.xml 里配置一個(gè) <multipart-config> ,不過(guò)借助于spring-boot的自動(dòng)配置,你什么都不必做。直接寫控制器類,我們?cè)?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 注解,證明這是一個(gè)controller,每個(gè)方法前添加了 @getmapping 和 @postmapping 分別相應(yīng)get和post請(qǐng)求。

首先是 @getmapping("/") ,方法 listuploadedfiles ,顧名思義,顯示文件列表,這里我們借助于storageservice遍歷文件夾下的所有文件,并且用map方法提合成了鏈接和文件名列表,返回了一個(gè)linker對(duì)象的數(shù)組,linker對(duì)象是一個(gè)簡(jiǎn)單pojo,只包含下面兩部分:

?
1
2
private string fileurl;
private string filename;

這個(gè)方法包含了對(duì)java8中stream的使用,如果有不理解的可以看看這篇文章 java8 特性詳解(二) stream api .

接下來(lái)是 @getmapping("/files/{filename:.+}") ,方法是 servefile ,該方法提供文件下載功能,還是借助于storageservice,后面會(huì)貼出storageservice的代碼。最后使用responseentity,把文件作為body返回給請(qǐng)求方。

@postmapping("/") 的 handlefileupload 使用post請(qǐng)求來(lái)上傳文件,參數(shù) @requestparam("file") 提取網(wǎng)頁(yè)請(qǐng)求里的文件對(duì)象,還是使用storageservice來(lái)保存對(duì)象,最后使用重定向來(lái)刷新網(wǎng)頁(yè),并且給出成功上傳的message。

4. 文件處理

上面controller調(diào)用的很多方法由storageservice提供,我們定義一個(gè)接口,包含以下方法:

?
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();
}

因?yàn)槲疫@里只是借助于本地文件系統(tǒng)處理文件的長(zhǎng)傳下載,所以有了以下實(shí)現(xiàn)類:

?
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());
  }
}

這個(gè)類也基本運(yùn)用了java的nio,使用path對(duì)象定義了location用于文件的默認(rèn)保存路徑。

先看 store 方法,store接受一個(gè)multipartfile對(duì)象作為參數(shù),想比于傳統(tǒng)jsp中只是傳二進(jìn)制字節(jié)數(shù)組,multipartfile提供了很多方便調(diào)用的方法讓我們可以獲取到上傳文件的各項(xiàng)信息:

?
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對(duì)應(yīng)的path里,當(dāng)然我們也可以使用transferto方法保存文件, file.transferto(this.rootlocation.resolve(filename).tofile());

loadall方法加載該路徑下的所有文件path信息, loadasresource 則是加載文件為一個(gè)resource對(duì)象,再看controller的代碼,最后是接受一個(gè)resource對(duì)象作為body返回給請(qǐng)求方。

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> 標(biāo)簽內(nèi)的內(nèi)容, <form method="post" action="/" enctype="multipart/form-data"> enctype 一定要寫成 multipart/form-data ,使用post上傳文件,原有的上傳控件很丑,所以做了一個(gè)text+input放在表面,在下面放了一個(gè)隱形的上傳文件的input,可以自己看看代碼,本文就不啰嗦了。

下面還放了一個(gè)list用于展示文件列表,這里我們獲取到服務(wù)端提供的linkers對(duì)象,不斷foreach就可以獲得里面的兩個(gè)元素fileurl和filename。

這里jquery換成了微軟的cdn,webjars的總是引入不進(jìn)來(lái),不知道什么原因。

其它設(shè)置

在 src/main/resources/application.properties 里設(shè)置上傳文件大小限制

?
1
2
spring.http.multipart.max-file-size=128mb
spring.http.multipart.max-request-size=128mb

另外在``還設(shè)置了文件默認(rèn)保存路徑:

?
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的設(shè)置,在application的那個(gè)類中要添加上

?
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);
 }
}

總結(jié)

以上所述是小編給大家介紹的spring boot + thymeleaf 實(shí)現(xiàn)文件上傳下載功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)服務(wù)器之家網(wǎng)站的支持!

原文鏈接:https://juejin.im/post/5a326dcaf265da431048685e

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 脱女学小内内摸出水网站免费 | 青青在线视频观看 | 亚洲精品午夜级久久久久 | 女同志freelesvoices | 美女的让男人桶爽30分钟的 | 精品国产剧情在线观看 | brazzersvideo欧美最新 | 日本欧美不卡一区二区三区在线 | 日本一二线不卡在线观看 | 久久re这里精品23 | 亚洲天堂男人的天堂 | 成年男女免费大片在线观看 | 美女的隐私无遮挡撒尿 | 人人爽人人看 | 果冻传媒天美传媒网址入口 | 国产精品99精品久久免费 | 91免费精品国自产拍在线可以看 | 久久国产精品二区99 | 四虎影院网址大全 | chinese男性厕所撒尿合集 | 91在线亚洲精品一区 | 男人天堂a | 天堂资源wwww在线看 | 四虎成人永久地址 | 交换性关系中文字幕6 | 99视频久久精品久久 | 私人影院免费 | 亚洲国产欧美久久香综合 | 久久精品国产在热亚洲完整版 | 亚洲国产精品高清在线 | 欧美在线一 | 王淑兰李思雨李铁柱乡村小说免费 | 99久久国产亚洲综合精品 | 特黄特a级特别特级特毛片 特黄a级三级三级野战 | 日韩操比视频 | 91大神第九部红酒气质女 | 门房秦大爷在线阅读 | a级动漫| 日本不卡免费新一二三区 | www四虎影院 | 韩国三级年轻小的胰子完整 |