# (一)概述 {#一-概述}
文件上传下载一直都是一个系统最常用也是最基本的功能点,刚好最近公司的项目上有用到这个功能,于是自己就用SpringBoot也写了一个简化的版本,已实现文件的上传和下载功能。
# (二)创建项目 {#二-创建项目}
首先创建一个SpringBoot的项目,接着引入相关的依赖,因为涉及到数据库的操作,所以依赖会比较多一些。
# 2.1 依赖引入 {#_2-1-依赖引入}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
# 2.2 接口通用返回类编写 {#_2-2-接口通用返回类编写}
编写一个接口的通用返回体,这个在之前的博客中我专门写过,现在就直接拿来用了:
public enum ResponseCode {
// 系统模块
SUCCESS(0, "操作成功"),
ERROR(1, "操作失败"),
SERVER_ERROR(500, "服务器异常"),
// 通用模块 1xxxx
ILLEGAL_ARGUMENT(10000, "参数不合法"),
REPETITIVE_OPERATION(10001, "请勿重复操作"),
ACCESS_LIMIT(10002, "请求太频繁, 请稍后再试"),
MAIL_SEND_SUCCESS(10003, "邮件发送成功"),
// 用户模块 2xxxx
NEED_LOGIN(20001, "登录失效"),
USERNAME_OR_PASSWORD_EMPTY(20002, "用户名或密码不能为空"),
USERNAME_OR_PASSWORD_WRONG(20003, "用户名或密码错误"),
USER_NOT_EXISTS(20004, "用户不存在"),
WRONG_PASSWORD(20005, "密码错误"),
// 文件模块 3xxxx
FILE_EMPTY(30001,"文件不能空"),
FILE_NAME_EMPTY(30002,"文件名称不能为空"),
FILE_MAX_SIZE(30003,"文件大小超出"),
;
ResponseCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Integer code;
private String msg;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
返回体:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private int code;
private String message;
private Object data;
}
# 2.3 配置一下解决跨域问题的配置类 {#_2-3-配置一下解决跨域问题的配置类}
在SpringBoot中有多种解决跨域的方法,这里选择其中一种
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
到这里为止,基本的项目配置就算结束了,接下来就是功能的实现了。
# (三)实现文件上传下载 {#三-实现文件上传下载}
# 3.1 创建表 {#_3-1-创建表}
首先创建一张表来记录文件的路径、名称、后缀等信息:
CREATE TABLE `file` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`filePath` varchar(255) DEFAULT NULL,
`fileName` varchar(255) DEFAULT NULL,
`fileSuffix` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
# 3.2 编写实体类 {#_3-2-编写实体类}
写一个文件对象,和数据库中的字段相对应:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class Files implements Serializable {
private static final long serialVersionUID=1L;
/**
* 文件存储路径
*/
private String filePath;
/**
* 文件名称
*/
private String fileName;
/**
* 文件后缀名
*/
private String fileSuffix;
}
# 3.3 配置application.properties {#_3-3-配置application-properties}
在配置文件中将服务端口,数据库连接方式以及文件的保存路径配置一下:
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/test7?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/*.xml
file.save-path=E:/temp/files
# 3.4 编写Controller {#_3-4-编写controller}
新建一个类叫FileController,用来写接口,文件上传下载接口的代码已经给了注释,Spring中提供了一个MultipartFile类,用来接收前台传过来的文件,这里直接使用即可。
@RestController
@RequestMapping("/api")
public class FileController {
@Autowired
private FileService fileService;
@RequestMapping(value = "/upload",method = RequestMethod.POST)
public Result upLoadFiles(MultipartFile multipartFile){
if (multipartFile.isEmpty()){
return new Result(ResponseCode.FILE_EMPTY.getCode(),ResponseCode.FILE_EMPTY.getMsg(),null);
}
return fileService.upLoadFiles(multipartFile);
}
@RequestMapping(value = "/download/{id}",method = RequestMethod.GET)
public void downloadFiles(@PathVariable("id") String id, HttpServletRequest request, HttpServletResponse response){
OutputStream outputStream=null;
InputStream inputStream=null;
BufferedInputStream bufferedInputStream=null;
byte[] bytes=new byte[1024];
Files files = fileService.getFileById(id);
String fileName = files.getFileName();
// 获取输出流
try {
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
response.setContentType("application/force-download");
inputStream=fileService.getFileInputStream(files);
bufferedInputStream=new BufferedInputStream(inputStream);
outputStream = response.getOutputStream();
int i=bufferedInputStream.read(bytes);
while (i!=-1){
outputStream.write(bytes,0,i);
i=bufferedInputStream.read(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (inputStream!=null){
inputStream.close();
}
if (outputStream!=null){
outputStream.close();
}
if (bufferedInputStream!=null){
bufferedInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
所有的业务都写在service中,因此需要有fileService接口以及fileServiceImpl实现类:
public interface FileService {
/**
* 文件上传接口
* @param file
* @return
*/
Result upLoadFiles(MultipartFile file);
/**
* 根据id获取文件
* @param id
* @return
*/
Files getFileById(String id);
/**
* 根据id获取数据流
* @param files
* @return
*/
InputStream getFileInputStream(Files files);
}
fileServiceImpl实现类:
@Service
public class FileServiceImpl implements FileService {
@Value("${file.save-path}")
private String savePath;
@Autowired
private FileMapper fileMapper;
@Override
public Result upLoadFiles(MultipartFile file) {
//设置支持最大上传的文件,这里是1024*1024*2=2M
long MAX_SIZE=2097152L;
//获取要上传文件的名称
String fileName=file.getOriginalFilename();
//如果名称为空,返回一个文件名为空的错误
if (StringUtils.isEmpty(fileName)){
return new Result(ResponseCode.FILE_NAME_EMPTY.getCode(),ResponseCode.FILE_NAME_EMPTY.getMsg(),null);
}
//如果文件超过最大值,返回超出可上传最大值的错误
if (file.getSize()>MAX_SIZE){
return new Result(ResponseCode.FILE_MAX_SIZE.getCode(),ResponseCode.FILE_MAX_SIZE.getMsg(),null);
}
//获取到后缀名
String suffixName = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".")) : null;
//文件的保存重新按照时间戳命名
String newName = System.currentTimeMillis() + suffixName;
File newFile=new File(savePath,newName);
if (!newFile.getParentFile().exists()){
newFile.getParentFile().mkdirs();
}
try {
//文件写入
file.transferTo(newFile);
} catch (IOException e) {
e.printStackTrace();
}
//将这些文件的信息写入到数据库中
Files files=new Files(newFile.getPath(),fileName,suffixName);
fileMapper.insertFile(files);
return new Result(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),"数据上传成功");
}
//根据id获取文件信息
@Override
public Files getFileById(String id) {
Files files = fileMapper.selectFileById(id);
return files;
}
//将文件转化为InputStream
@Override
public InputStream getFileInputStream(Files files) {
File file=new File(files.getFilePath());
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
# 3.5 对数据库的操作 {#_3-5-对数据库的操作}
需要将数据写入到数据库中,这里用到的是mybatis,新建一个FileMapper接口:
@Mapper
@Repository
public interface FileMapper {
/**
* 将数据信息插入到数据库
* @param files
*/
void insertFile(Files files);
/**
* 根据id获取文件
* @param id
* @return
*/
Files selectFileById(String id);
}
编写对应的xml文件
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javayz.fileuploadanddownload.mapper.FileMapper">
<insert id="insertFile" parameterType="com.javayz.fileuploadanddownload.entity.Files">
insert into file(filepath,filename,filesuffix) values(#{filePath},#{fileName},#{fileSuffix});
</insert>
<select id="selectFileById" parameterType="string" resultType="com.javayz.fileuploadanddownload.entity.Files">
select * from file where id=#{id};
</select>
</mapper>
代码已上传至github,欢迎自取:github (opens new window)
# (四)测试 {#四-测试}
将项目运行起来,首先测试文件上传,通过postman直接上传一个文件
点击send后得到操作成功的返回值,我们可以在自己设置的路径下看到这个文件,同时在数据库中也存在该文件的信息:
接下来测试文件下载,因为是get请求,直接在浏览器中访问: http://localhost:8080/api/download/4 即可调用下载。