文件上传和下载在项目中经常用到,这里主要学习SpringBoot完成单个文件上传/下载,批量文件上传的场景应用。结合mysql数据库、jpa数据层操作、thymeleaf页面模板。
一、准备
添加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
|
配置文件application.yml
server: port: 8081 tomcat: max-swallow-size: 1MB spring: servlet: multipart: enabled: true max-file-size: 50MB max-request-size: 100MB file-size-threshold: 0 location: /test datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: root jpa: database: mysql show-sql: true hibernate: ddl-auto: update thymeleaf: enabled: true encoding: UTF-8 mode: HTML5 prefix: classpath:/templates/ suffix: .html cache: false check-template-location: true servlet: content-type: text/html resources: static-locations: classpath:/
niaobulashi: file: path: D:\workspace\javaProject\spring-boot-learning\spring-boot-22-updownload\doc extension: .gif,.jpeg,.png,.jpg,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.rar,.tif
|
最下面是自定义的配置属性,定义了文件存放路径和上传文件允许的后缀名称。
需要注意的是:niaobulashi.file.path
,为你磁盘上的目录,根据你实际的目录修改。
数据库表sys_file_info
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `sys_file_info`; CREATE TABLE `sys_file_info` ( `file_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文件id', `file_name` varchar(50) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '文件名称', `file_path` varchar(255) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '文件路径', `file_size` varchar(100) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '文件大小', PRIMARY KEY (`file_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文件信息表';
|
二、代码实现
结构目录
页面file.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <p>1、单文件上传</p> <form action="upload" method="POST" enctype="multipart/form-data"> 文件:<input type="file" name="file"/> <input type="submit"/> </form> <hr/> <p>2、文件下载</p> <form action="download" method="POST" enctype="multipart/form-data"> 文件ID:<input type="text" name="fileId"/> <input type="submit" value="下载文件"/> </form> <hr/> <p>3、多文件上传</p> <form action="batchUpload" method="POST" enctype="multipart/form-data"> 一次选择多个文件的多文件上传:<input type="file" name="files" multiple> <input type="submit"/> </form> </body> </html>
|
统一返回ResponseCode
@Data @AllArgsConstructor public class ResponseCode extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public static final String CODE_TAG = "code";
public static final String MSG_TAG = "msg";
public static final String DATA_TAG = "data";
public enum Type { SUCCESS(100), WARN(200), ERROR(300); private final int value;
Type(int value) { this.value = value; }
public int value() { return this.value; } }
private Type type;
private int code;
private String msg;
private Object data;
public ResponseCode(Type type, String msg) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); }
public ResponseCode(Type type, String msg, Object data) { super.put(CODE_TAG, type.value); super.put(MSG_TAG, msg); if (data !=null) { super.put(DATA_TAG, data); } }
public static ResponseCode success() { return ResponseCode.success("操作成功"); }
public static ResponseCode success(Object data) { return ResponseCode.success("操作成功", data); }
public static ResponseCode success(String msg) { return ResponseCode.success(msg, null); }
public static ResponseCode success(String msg, Object data) { return new ResponseCode(Type.SUCCESS, msg, data); }
public static ResponseCode warn(String msg) { return ResponseCode.warn(msg, null); }
public static ResponseCode warn(String msg, Object data) { return new ResponseCode(Type.WARN, msg, data); }
public static ResponseCode error() { return ResponseCode.error("操作失败"); }
public static ResponseCode error(String msg) { return ResponseCode.error(msg, null); }
public static ResponseCode error(String msg, Object data) { return new ResponseCode(Type.ERROR, msg, data); }
}
|
model实体类SysFileInfo
@Data @Entity @Table(name = "sys_file_info") public class SysFileInfo implements Serializable {
@Id @GeneratedValue private Integer fileId;
@Column(nullable = false) private String fileName;
@Column(nullable = false) private String filePath;
@Column(nullable = false) private Long fileSize; }
|
读取配置文件信息
@Data @Component public class GlobalProperties {
@Value("${niaobulashi.file.path}") private String serverPath;
@Value("${niaobulashi.file.extension}") private String extension;
}
|
dao层
public interface SysFileInfoDao extends JpaRepository<SysFileInfo, Integer> {
}
|
Controller层
关系的地方来了,其中有三部分:单文件上传、下载、批量文件上传
头部分
@Controller public class FileController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
@Autowired private SysFileInfoDao sysFileInfoDao;
@Autowired private GlobalProperties globalProperties;
@GetMapping("/") public String updatePage() { return "file"; } }
|
单文件上传
@PostMapping("/upload") @ResponseBody private ResponseCode upload(@RequestParam("file") MultipartFile file) throws Exception { String serverPath = globalProperties.getServerPath();
String extension = globalProperties.getExtension();
File filePath = new File(serverPath); logger.info("文件保存的路径为:" + filePath); if (!filePath.exists() && !filePath.isDirectory()) { logger.info("目录不存在,则创建目录:" + filePath); filePath.mkdir(); }
if (file.isEmpty()) { return ResponseCode.error("文件为空"); } if (file.getSize() <= 0) { return ResponseCode.error("文件大小为空,上传失败"); }
if (DEFAULT_MAX_SIZE != -1 && file.getSize() > DEFAULT_MAX_SIZE) { return ResponseCode.error("上传的文件不能大于50M"); }
String fileName = file.getOriginalFilename(); String fileExtension = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
if (!extension.contains(fileExtension)) { return ResponseCode.error("文件扩展名不正确"); }
SysFileInfo sysFileInfo = new SysFileInfo(); String saveFileName = System.currentTimeMillis() + fileExtension; File targetFile = new File(filePath, saveFileName);
logger.info("将文件保存到指定目录"); try { file.transferTo(targetFile); } catch (IOException e) { throw new Exception(e.getMessage()); }
sysFileInfo.setFileName(fileName); sysFileInfo.setFilePath(serverPath + "/" + saveFileName); sysFileInfo.setFileSize(file.getSize());
logger.info("新增文件数据"); sysFileInfoDao.save(sysFileInfo); return ResponseCode.success("上传成功"); }
|
下载
下载的逻辑,我在前端通过input输入框输入fileId,后台查询数据库来下载
正常情况下应该是列表,单选或者多选后下载文件的。
@PostMapping("/download") @ResponseBody public ResponseCode downloadFile(@RequestParam("fileId") Integer fileId, HttpServletRequest request, HttpServletResponse response) { logger.info("文件ID为:" + fileId); if (fileId == null) { return ResponseCode.error("请求参数不能为空"); } Optional<SysFileInfo> sysFileInfo = sysFileInfoDao.findById(fileId); if (sysFileInfo.isEmpty()) { return ResponseCode.error("下载的文件不存在"); } File file = new File(sysFileInfo.get().getFilePath()); String fileName = sysFileInfo.get().getFileName(); if (file.exists()) { response.setContentType("application/force-download"); response.addHeader("Content-Disposition", "attachment;fileName=" + fileName); byte[] buffer = new byte[1024]; FileInputStream fis = null; BufferedInputStream bis = null; try { fis = new FileInputStream(file); bis = new BufferedInputStream(fis); OutputStream os = response.getOutputStream(); int i = bis.read(buffer); while (i != -1) { os.write(buffer, 0, i); i = bis.read(buffer); } return ResponseCode.success("下载成功"); } catch (Exception e) { e.printStackTrace(); } finally { if (bis != null) { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } else { return ResponseCode.error("数据库查询存在,本地磁盘不存在文件"); } return ResponseCode.success("下载失败"); }
|
批量文件上传
@PostMapping("/batchUpload") @ResponseBody public ResponseCode batchUpload(@RequestParam("files") MultipartFile[] files) throws Exception { if (files == null) { return ResponseCode.error("参数为空"); } for (MultipartFile multipartFile : files) { upload(multipartFile); } return ResponseCode.success("批量上传成功"); }
|
至此项目完成,开始测试
三、测试
四、源码
emmm,私藏的可爱图片也给你们啦
源码地址:spring-boot-learning
欢迎star、fork,给作者一些鼓励