介紹:本次案例使用springboot項目實現了文件的上傳及下載,並且在windows環境和Linux環境下都適用。
一、功能預覽:
二、上傳功能實現:
2.1、先創建一個表,用來存儲上傳文件的一些基本信息。
2.2、創建一個springboot項目,並且導入下列依賴:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2.3、修改yml文件:
創建三個yml文件:
application-dev.yml:該配置文件是在windows環境下使用的
spring:
application:
name: file_upload_download
servlet:
multipart:
#上傳最大文件大小。值可以使用后綴“MB”或“KB”。指示兆字節或千字節大小。
max-file-size: 100MB
#最大請求大小可以是mb也可以是kb
max-request-size: 100MB
mvc:
static-path-pattern: /**
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 8099
application-prod.yml:該配置文件是在Linux環境下使用的
spring:
application:
name: file_upload_download
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
mvc:
static-path-pattern: /**
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 8099
使用那個配置文件直接在application.yml中進行切換即可:
spring:
profiles:
active: prod #dev
2.4、實體類:
package com.xct.file_upload_uownload.entity;
import lombok.Data;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-11-19 11:39
*/
@Data
public class FileInformation {
private Integer id;
private String title;
private String uploadDate;
private String imageName;
private String fileName;
}
2.5、dao層:mapper
創建一個FileMapper,提供將文件信息添加到數據庫,以及查詢數據庫中所有文件信息方法。
package com.xct.file_upload_uownload.dao;
import com.xct.file_upload_uownload.entity.FileInformation;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-11-19 11:42
*/
@Mapper
public interface FileMapper {
/**
* 添加
* @author xct
* @date 2020-11-20 16:17
* @param title
* @param uploadDate
* @param imageName
* @param fileName
* @return int
*/
@Insert("INSERT INTO file_information (title,upload_date,image_name,file_name) VALUES(#{title},#{upload_date},#{image_name},#{file_name})")
public int insert(@Param("title")String title,@Param("upload_date")String uploadDate,@Param("image_name")String imageName,@Param("file_name")String fileName);
//查詢
@Select("SELECT id,title,upload_date uploadDate,image_name imageName,file_name fileName from file_information")
public List<FileInformation> findAllFile();
}
2.6、服務層:上傳實現
創建一個FileService接口,FileServiceImpl實現FileService接口,提供文件上傳實現和查詢所有文件方法。
package com.xct.file_upload_uownload.service.impl;
import com.xct.file_upload_uownload.dao.FileMapper;
import com.xct.file_upload_uownload.entity.FileInformation;
import com.xct.file_upload_uownload.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-10-30 11:30
*/
@Service
public class FileServiceImpl implements FileService {
private static final Logger LOGGER= LoggerFactory.getLogger(FileService.class);
@Autowired
FileMapper fileMapper;
/**
* 文件上傳的實現方法
* @author xct
* @date 2020-11-20 16:41
* @param file
* @param image
* @param title
* @return java.lang.String
*/
@Override
public String uploadFile(MultipartFile file, MultipartFile image,String title) throws Exception {
String os = System.getProperty("os.name");
File imagePath; //封面圖片存放地址
File fileRealPath; //文件存放地址
if (os.toLowerCase().startsWith("win")) { //windows系統
String path = System.getProperty("user.dir"); //獲取項目相對路徑
fileRealPath = new File(path+"/src//main/resources/file");
imagePath = new File(path+"/src//main/resources/static/images");
}else{//linux系統
//獲取根目錄
//如果是在本地windows環境下,目錄為項目的target\classes下
//如果是linux環境下,目錄為jar包同級目錄
File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
if(!rootPath.exists()){
rootPath = new File("");
}
fileRealPath = new File(rootPath.getAbsolutePath()+"/file/");
imagePath = new File(rootPath.getAbsolutePath()+"/images");
}
//判斷文件夾是否存在
if(!fileRealPath.exists()){
//不存在,創建
fileRealPath.mkdirs();
}
if(!imagePath.exists()){
//不存在,創建
imagePath.mkdirs();
}
//獲取文件名稱
String fileName = file.getOriginalFilename();
String imageName = image.getOriginalFilename();
//創建文件存放地址
File resultPath = new File(fileRealPath+"/"+fileName);
if (resultPath.exists()){
LOGGER.warn("文件已經存在!");
return "false!";
}
//創建圖片存放地址
File imageResultPath = new File(imagePath+"/"+imageName);
if(imageResultPath.exists()){
LOGGER.warn("圖片已經存在!");
return "false!";
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
file.transferTo(resultPath);
image.transferTo(imageResultPath);
fileMapper.insert(title, sdf.format(new Date()), imageName, fileName);
System.out.println("absolutePath:"+fileRealPath.getCanonicalPath());
System.out.println("resultPath:"+resultPath.getCanonicalPath());
System.out.println("imageResultPath:"+imageResultPath.getCanonicalPath());
return "true!";
}
/**
* 查詢數據庫中所有文件信息的方法
* @author xct
* @date 2020-11-20 16:42
* @param
* @return java.util.List<com.xct.file_upload_uownload.entity.FileInformation>
*/
@Override
public List<FileInformation> getAllFile() {
return fileMapper.findAllFile();
}
}
2.7、控制器:
創建一個FileController,提供文件上傳的控制器和一個查詢所有文件后跳轉前端頁面的控制器。
package com.xct.file_upload_uownload.controller;
import com.xct.file_upload_uownload.entity.FileInformation;
import com.xct.file_upload_uownload.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-10-30 11:55
*/
@Controller
public class FileController {
private static final Logger LOGGER= LoggerFactory.getLogger(FileController.class);
@Autowired
private FileService fileService;
/**
* 文件上傳
* @author xct
* @date 2020-11-20 16:50
* @param file 文件
* @param fileImage 用於做封面的圖片
* @param title 標題
* @return java.lang.String
*/
@PostMapping("/uploadFile")
public String uploadFile(@RequestParam("file")MultipartFile file,@RequestParam("fileImage")MultipartFile fileImage,@RequestParam("title")String title){
if(file.isEmpty()){
LOGGER.error("上傳失敗,請選擇文件!");
return "redirect:/getAllFile";
}
try {
String result = fileService.uploadFile(file,fileImage,title);
LOGGER.info(result);
return "redirect:/getAllFile";
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("文件上傳失敗!");
return "redirect:/getAllFile";
}
}
/**
* 查詢所有的文件信息
* @author xct
* @date 2020-11-19 16:28
* @param
* @return java.lang.String
*/
@RequestMapping("/getAllFile")
public String getAllFile(HttpServletRequest request){
List<FileInformation> allFile = fileService.getAllFile();
request.setAttribute("fileList", allFile);
return "fileDownload";
}
}
2.8、前端頁面:
創建一個前端頁面,使用表單將用戶輸入的文件信息傳輸到controller。
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! -->
<title>Bootstrap 101 Template</title>
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
html,body{
height: 100%;
width: 100%;
margin: 0 auto;
}
.titleClass{
margin-top:8px;
color: white;
font-weight: bolder;
}
.timeClass{
margin-top: 25px;
margin-bottom: 10px;
color: grey;
font-size: 14px;
}
.contentTd{
padding-left: 10px;
padding-right: 10px;
width: 150px!important;
height: 150px;
}
tr{
margin-top: 10px;
margin-bottom: 60px;
display: block;
}
.buttonP{
padding-top: 20px;
}
.imageTd{
width: 267px!important;
height: 150px;
}
.imageTd img{
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<div style="width: 100%;height: 100%;background-color: #0B656D">
<table align="center" style="width: 85%;">
<th:block th:each="usr,status:${fileList}">
<p th:remove="tag" th:utext="${(status.index+1)%3==1 ? '<tr>':''}"/>
<td class="imageTd">
<img th:src="@{/images/{imageName}(imageName=${usr.imageName})}">
</td>
<td class="contentTd">
<p class="titleClass"><span th:text="${usr.title}"></span></p>
<p class="timeClass"><span th:text="${usr.uploadDate}"></span></p>
<p class="buttonP">
<!--<a href="/download/2018年度中國城市活力研究報告.pdf" download>-->
<a th:href="@{/download/{fileName}(fileName=${usr.fileName})}" download>
<button type="button" class="btn btn-primary">下載</button>
</a>
</p>
</td>
<p th:remove="tag" th:utext="${(status.index+1)%5==0 ? '</tr>':''}"/>
</th:block>
<tr>
<td>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">
上傳
</button>
</td>
</tr>
</table>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">文件上傳</h4>
</div>
<div class="modal-body">
<form enctype="multipart/form-data" method="post" action="/uploadFile">
<div class="form-group">
<label for="exampleInputEmail1">文件標題</label>
<input type="text" class="form-control" id="exampleInputEmail1" placeholder="文件標題" name="title">
</div>
<div class="form-group">
<label for="exampleInputFile">文件</label>
<input type="file" id="exampleInputFile" name="file">
<p class="help-block">上傳文件</p>
</div>
<div class="form-group">
<label for="exampleInputFile">文件封面</label>
<input type="file" id="fileImage" name="fileImage">
<p class="help-block">上傳文件封面</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
我這里使用了Bootstrp的模態框作為表單彈出,以及做了一點樣式。
這段代碼是當一行(<tr>
)里面有三個單元格<td>
之后就會添加一個<tr>
,也就是下圖的效果:
參考博客:https://blog.csdn.net/iteye_19045/article/details/97809707
2.9、配置虛擬路徑映射:
其實到這里,上傳就基本已經實現了,但是會發現上傳成功之后,圖片不會馬上在頁面上顯示出來,必須重啟項目之后才會顯示,這是因為對服務器的保護措施導致的,服務器不能對外部暴露真實的資源路徑,需要配置虛擬路徑映射訪問。
創建一個ResourceConfigAdapter:
package com.xct.file_upload_uownload.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-11-20 11:03
*/
@Configuration
public class ResourceConfigAdapter extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//在windows環境下的圖片存放資源路徑
String winPath = System.getProperty("user.dir")+"\\src\\main\\resources\\static\\images\\";
//在Linux環境下的圖片存放資源路徑
String linuxPath = "/usr/local/my_project/images/";
String os = System.getProperty("os.name");
if (os.toLowerCase().startsWith("win")) { //windows系統
//第一個方法設置訪問路徑前綴,第二個方法設置資源路徑
registry.addResourceHandler("/images/**").
addResourceLocations("file:"+winPath);
}else{//linux系統
registry.addResourceHandler("/images/**").
addResourceLocations("file:"+linuxPath);
}
super.addResourceHandlers(registry);
}
}
addResourceHandler()里配置需要映射的文件夾,此處代表映射文件夾images下的所有資源。
addResourceLocations()配置資源在系統中的路徑,使用絕對路徑,格式為“file:你的路徑”
到此,文件上傳的功能就已經完成了。
三、下載功能實現:
參考自:https://www.bilibili.com/read/cv5604214/
修改yml文件
在application-dev.yml添加如下配置:
file:
doc-dir: src/main/resources/file/
在application-prod.yml添加:
file:
doc-dir: file/
該配置就是待下載文件存放在服務器上的目錄,為相對路徑,dev配置文件中表示為當前項目的src/main/resources/file/下:
prod配置文件則表示與當前項目(jar包)同級:
將屬性與 pojo 類自動綁定
springboot 中的注解 @ConfigurationProperties
可以將 application 中定義的屬性與 pojo 類自動綁定。所以,我們需要定義一個 pojo 類來做 application 中 file.doc-dir=doc/
的配置綁定:
package com.xct.file_upload_uownload.entity;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-10-30 11:33
*/
@ConfigurationProperties(prefix = "file")
@Data
public class FileProperties {
private String docDir;
}
注解 @ConfigurationProperties(prefix = "file")
在 springboot 應用啟動時將 file 為前綴的屬性與 pojo 類綁定,也就是將 application.yml
中的file.doc-dir
與 FileProperties 中的字段 docDir 做了綁定。
激活配置屬性
在啟動類或其他配置類(@Configuration注解標記)上加入 @EnableConfigurationProperties 即可讓 ConfigurationProperties 特性生效。
package com.xct.file_upload_uownload;
import com.xct.file_upload_uownload.entity.FileProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties({FileProperties.class})
public class FileUploadUownloadApplication {
public static void main(String[] args) {
SpringApplication.run(FileUploadUownloadApplication.class, args);
}
}
自定義異常
在服務層,我們拋出自定義的文件異常 FileException.
package com.xct.file_upload_uownload.exception;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-10-30 11:51
*/
public class FileException extends RuntimeException {
public FileException(String message) {
super(message);
}
public FileException(String message, Throwable cause) {
super(message, cause);
}
}
服務層**
服務層的主要工作是把文件作為 IO 資源加載。注意,在 Service 實現類的構造方法中要使用 @Autowired 注入前面定義好的屬性綁定類 FileProperties.
package com.xct.file_upload_uownload.service.impl;
import com.xct.file_upload_uownload.entity.FileProperties;
import com.xct.file_upload_uownload.exception.FileException;
import com.xct.file_upload_uownload.service.FileService;
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 java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-10-30 11:30
*/
@Service
public class FileServiceImpl implements FileService {
private final Path filePath;
@Autowired
public FileServiceImpl(FileProperties fileProperties) {
filePath = Paths.get(fileProperties.getDocDir()).toAbsolutePath().normalize();
}
@Override
public Resource loadFileAsResource(String fileName){
Path path = filePath.resolve(fileName).normalize();
System.out.println(path+"---------------------------------------------------");
try {
UrlResource resource = new UrlResource(path.toUri());
if (resource.exists()) {
return resource;
}
throw new FileException("file " + fileName + " not found");
} catch (MalformedURLException e) {
throw new FileException("file " + fileName + " not found", e);
}
}
}
控制層**
在控制層我們將以 spring 框架的 ResponseEntity 類作為返回值傳給前端,其中泛型為 spring io 包的 Resource 類,這意味着返回內容為 io 流資源;並在返回體頭部添加附件,以便於前端頁面下載文件。
package com.xct.file_upload_uownload.controller;
import com.xct.file_upload_uownload.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
/**
* @author Xct1194542884
* @title: xct
* @projectName file_upload_uownload
* @description: TODO
* @date 2020-10-30 11:55
*/
@Controller
public class FileController {
private static final Logger LOGGER= LoggerFactory.getLogger(FileController.class);
@Autowired
private FileService fileService;
@RequestMapping("/downloading")
public String downloading(){
return "fileDownload";
}
/**
* 下載
* @author xct
* @date 2020-10-30 17:26
* @param fileName
* @param request
* @return org.springframework.http.ResponseEntity<org.springframework.core.io.Resource>
*/
@GetMapping("/download/{fileName}")
@ResponseBody
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) throws UnsupportedEncodingException {
System.out.println("====================================================");
Resource resource = fileService.loadFileAsResource(fileName);
String contentType = null;
try {
System.out.println("=================================="+resource.getFile().getAbsolutePath());
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException e) {
LOGGER.error("無法獲取文件類型", e);
}
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + new String(resource.getFilename().getBytes("UTF-8"),"iso-8859-1") + "\"")
.body(resource);
}
}
前端
和上傳使用一個html頁面即可。
String contentType = null;
try {
System.out.println("=================================="+resource.getFile().getAbsolutePath());
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException e) {
LOGGER.error("無法獲取文件類型", e);
}
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + new String(resource.getFilename().getBytes("UTF-8"),"iso-8859-1") + "\"")
.body(resource);
}
}
#### **前端**
和上傳使用一個html頁面即可。
到此一個簡單的文件上傳與下載的功能就實現了。