介绍:本次案例使用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>
,也就是下图的效果:
参考博客:
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页面即可。
到此一个简单的文件上传与下载的功能就实现了。