文章目录
前言 : 本文基于SpringBoot进行讲解
在企业工作中,我们经常会遇到表单数据携带Excel附件的上传、单附件下载、多附件打包下载,如果你不知道如何完成这些工作的话,那么下面的文章绝对适合你,足够解决你后端接口的所有问题,如果这篇文章你觉得对你有帮助,别忘了点个关注的同时给小编点个赞,之后我会继续更新一些企业级项目的功能实现文章!!!
一、环境搭建
在编写开发接口前,我们需要将文件上传的依赖、application.properties中的配置上传配置都弄好。1.1 添加依赖
<!--上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
1.2 配置文件
# 开启文件上传
spring.servlet.multipart.enabled=true
# 设置文件最大上传单位 默认为10MB
spring.servlet.multipart.max-file-size=200MB
spring.servlet.multipart.max-request-size=200MB
就此我们的第一步算是完成了。
二、附件的批量上传
2.1 MultipartFile : “多组件的文档”
一般来讲如果你在前端想要使用表单形式进行文件上传都要使用MultipartFile类,我相信大家对于这个也并不陌生,那么我也不对其进行详细的讲解,下面主要罗列它的一些主要的方法。
1、getOriginalFileName方法
getOriginalFileName方法获取的是文件的完整名称,包括文件名称+文件拓展名。
2、getContentType方法
getContentType方法获取的是文件的类型,注意是文件的类型,不是文件的拓展名。
3、isEmpty方法
isEmpty方法用来判断传入的文件是否为空,如果为空则表示没有传入任何文件。
4、getSize方法
getSize方法用来获取文件的大小,单位是字节。
5、getBytes方法
getBytes方法用来将文件转换成一种字节数组的方式进行传输,会抛出IOException异常。
6、getInputStream方法
getInputStream方法用来将文件转换成输入流的形式来传输文件,会抛出IOException异常。
7、transferTo方法
transferTo方法用来将接收文件传输到给定目标路径,会抛出IOException、IllegalStateException异常。该方法在实际项目开发中使用较多。
2.2 逻辑讲解
当我们要进行文件上传的时候,一般在企业开发中,我们需要将它们的路径存储到数据库,因为这些上传的文件一般都是需要下载的,记录它们便于进行管理与下载。那么我个人习惯于这样建立数据表
id : id值
dir_path : 存储路径
file_name : 文件名
file_newName : 存储到服务器的文件名,在企业开发中,一般上传的文件是存储你公司的服务器上,而为了防止重复文件名,我们都会将它们进行重命名。
uploadTime : 上传时间
fid : 外键id,一般多附件上传都是基于某一个消息下,所以要将它们关联上,如果你没有这样的需求这个外键也不需要加
2.3 实体类
/**
* @author 码不多
* @version 1.0
* @description: 附件实体类
* @date 2021/8/18 17:37
*/
//Lombok
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class FileTemp {
private int id; //模板id
private String dir_path; //模板父路径
private String file_name; //模板文件名
private String file_newName; //模板的新名字(存储在服务器防止重名)
private Timestamp uploadTime; //上传时间
private int fid; //message的对应id外键
private String about; //message的名称
}
代码解释 :使用LomBok实体操作,其中的about是表单中的消息名称,可以理解为上传的这几个附件都是属于about这个名称下的附件。是一对多的关系。 如果你只是批量上传的需求,将此字段直接去除即可。
2.4 Mapper
Mybatis的映射xml
<!--添加上传的附件路径、时间-->
<insert id="addFileTemp" parameterType="FileTemp">
insert into filetemp(dir_path,file_name,file_newName,uploadTime,fid)
values(#{dir_path},#{file_name},#{file_newName},#{uploadTime},#{fid})
</insert>
Mapper映射接口
/**
* @author 码不多
* @version 1.0
* @description: 附件操作映射类
* @date 2021/8/18 17:36
*/
@Mapper
@Repository
public interface FileTempMapper {
/**
* 功能描述: 存储上传的附件路径
* @author 码不多
* @date 2021/8/18
* @param fileTemp
* @return void
*/
public void addFileTemp(FileTemp fileTemp);
2.5 Service层
业务层就是单纯的依赖注入调用方法此处只贴实现类了,这个感觉大家应该都会的。
/**
* @author 码不多
* @version 1.0
* @description: 附件上传业务层
* @date 2021/8/18 17:46
*/
@Service
public class FileTempUploadServiceImpl implements FileTempUpLoadService {
//DI
@Autowired
private FileTempMapper fileTempMapper ;
/**
* 功能描述: 存储上传的附件路径
* @author 码不多
* @date 2021/8/18
* @param fileTemp
* @return void
*/
public void addFileTemp(FileTemp fileTemp) {
//调用方法存储路径
fileTempMapper.addFileTemp(fileTemp);
}
}
2.6 Controller控制器 :
注意我这个文件上传是带表单数据一起从前端传递过来的, 如果你只是单独的文件上传,不携带表单信息,将我所有的表单信息有关的代码删除即可。(跟Message有关的代码!)
先贴出代码,下面会有讲解 :
**
* @author 码不多
* @version 1.0
* @description: 批量上传附件控制器
* @date 2021/8/17 14:37
*/
@CrossOrigin
@RestController
@Slf4j
@Transactional
@RequestMapping("/sys")
public class UploadController {
//定义模板上传路径,此处使用配置文件配置父路径
@Value("${fileTemplate.path}")
private String upload_folder;
//DI
@Autowired
private AnnounceServiceImpl announceService;
@Autowired
private FileTempUploadServiceImpl fileTempService;
@PostMapping("/announce")
/**
* 功能描述: 发布公告,批量上传附件
* @author 码不多
* @date 2021/8/18
* @param file 上传对象
@param message 公告的实体类
@param dir_name 文件的父路径
@param responseMsg 响应给前端的消息
* @return java.lang.String
*/
// @LogAnnotationMethod(module = "发布公告",operator = "批量上传了附件") : 这是我自定义的注解日志,大家用的时候去除,日志的编写我也有写过,大家可以看看以往的日志文章
@Transactional
public String announce(@RequestParam(value = "files") MultipartFile[] file,
Message message,
String responseMsg
){
//判断文件是否为空
if (file.length() == 0) {
//将错误消息返回
responseMsg = "当前附件为空!";
return responseMsg;
}
//定义标志位
boolean flag = true;
//遍历上传文件
for(int i=0;i<file.length;i++) {
//获取上传的文件名
String name = file[i].getOriginalFilename();
//进一步判断文件是否为空(即判断其大小是否为0或其名称是否为null)验证文件名是否合格
//前端如已经进行限制,此段代码可以不要
long size = file[i].getSize(); //获取文件大小
if (name == null || ("").equals(name) && size == 0 && !ExcelValidatorUtil.validateExcel(name)) {
//将错误消息返回
responseMsg = "文件格式不正确!请使用.xls或.xlsx后缀文档。";
return responseMsg;
}
//创建文件对象
File temp = new File(upload_folder);
//判断模板文件夹是否存在
if (!temp.exists()){
//创建文件夹
temp.mkdirs();
}
//获取原始文件的.的索引
int endIndexOf = name.lastIndexOf(".");
//获取上传的文件后缀
String endFile = name.substring(endIndexOf,name.length());
//创建新的文件名使用uuid随机数
String newName = randomUUID().toString()+endFile;
//创建本地File对象
File localFile = new File(upload_folder+newName);
try {
//把上传的文件保存至本地
file[i].transferTo(localFile);
//上传成功
responseMsg = name+"附件上传成功!公告发布完成。";
//判断模板是否上传成功,成功了才添加标题到数据库
if (responseMsg.equals(name+"附件上传成功!公告发布完成。")){
//调用方法获取当前时间,将时间给它们set
Timestamp dateTime = DateConverter.getDate();
//执行添加标题、内容添加、通知时间添加
message.setNotice_time(dateTime);
//当发布过一次公告就不向通知表中添加数据
if(flag){
//添加方法执行sql
announceService.insertTitleAndAnnounce(message);
//将flag变为flase
flag = false;
}
//创建对象并set,路径参数
FileTemp fileTemp = new FileTemp();
fileTemp.setDir_path(upload_folder);
fileTemp.setFile_name(name);
fileTemp.setFile_newName(newName);
fileTemp.setUploadTime(dateTime);
//获取message的自增id添加到从表外键
Integer id = message.getId();
fileTemp.setFid(id);
//执行sql
fileTempService.addFileTemp(fileTemp);
}
//将其返回
return responseMsg;
}catch (IOException e){
//上传失败
e.printStackTrace();
responseMsg = "发布公告失败: "+name+"附件上传失败!";
return responseMsg;
}
}
return responseMsg;
}
}
2.6.1 自定义路径详解 @Value
我在代码中定义的路径都并不是在代码中硬编码的,这样在后期我们需要修改存储路径的时候也不需要去改代码,我们的@Value注解是用来读取properties文件值的,SpringEL表达式,这里不做详细讲解,application.properties配置如下 :
###上传路径
#动态配置上传的附件的父路径,采用@Value赋值方式
fileTemplate.path=F://上传的excel附件/附件/
2.6.2 Controller注解讲解
@CrossOrigin : 如果你是前后端分离,需要添加这个注解,如果不是直接去掉即可。
@RestController : 相当于@ResponseBody + @Controller
@Slf4j : 日志门面,在以前的文章有讲过,如果不需要直接去掉即可。
@Transactional : 事物,如果不需要直接去掉即可。
@RequestMapping("/sys") : 这个大家都懂,就不说了。
2.6.3 参数讲解
public String announce(@RequestParam(value = "files") MultipartFile[] file,
Message message,
String responseMsg
){
1. MultipartFile[] file: 这个是接收你前端上传的文件数组
2. Message message : 我写的这个批量上传是表单上传,表单里的其他一些数据,都是存在Message这个实体类中的,这个类就是用来接收前端返回的其他input输入框的信息,并存储到DB中。这些根据你的项目需求是你自己定义的,要根据实际业务情况而定。
3. responseMsg : 响应给前端的消息,这里我懒了,直接用了个变量,这里如何规范写的话应该是个响应消息类
2.6.4 代码主体讲解1:
if (file.length() == 0) {
//将错误消息返回
responseMsg = "当前附件为空!";
return responseMsg;
}
判断前端返回给我们的文件数组是否有数据,没有就给它个响应告诉它没有数据
//定义标志位
boolean flag = true;
因为我是两张数据表关联,一个公告消息表、一个路径表,下面会调用Service方法执行向公告表插入about公告信息,
但是它是遍历的执行的而我只希望公告插入执行一次,所以在循环外定义标志位等执行完插入about公告就将其改为flag = false;它就不会再执行。
如果你只是单独的文件上传,不携带表单信息,将我所有的表单信息有关的代码删除即可。
//遍历上传文件
for(int i=0;i<file.length;i++) {
//获取上传的文件名
String name = file[i].getOriginalFilename();
//进一步判断文件是否为空(即判断其大小是否为0或其名称是否为null)验证文件名是否合格
//前端如已经进行限制,此段代码可以不要
long size = file[i].getSize(); //获取文件大小
if (name == null || ("").equals(name) && size == 0 && !ExcelValidatorUtil.validateExcel(name)) {
//将错误消息返回
responseMsg = "文件格式不正确!请使用.xls或.xlsx后缀文档。";
return responseMsg;
}
上面代码是效验文件的格式是否为excel,如果你前端的UI框架有限制格式,这些代码就不需要在写了。
File temp = new File(upload_folder);
if (!temp.exists()){
temp.mkdirs();
}
判断文件目录是否存在,不存在就创建
2.6.5代码主体讲解2 :
int endIndexOf = name.lastIndexOf(".");
String endFile = name.substring(endIndexOf,name.length());
String newName = randomUUID().toString()+endFile;
上面代码是获取你上传文件的后缀 .xlsx、.xls,并且生成新的UUID随机文件名字
File localFile = new File(upload_folder+newName);
创建File对象,将路径和你随机生成的新的文件名传递进去。
try {
file[i].transferTo(localFile);
//上传成功
responseMsg = name+"附件上传成功!公告发布完成。";
//判断模板是否上传成功,成功了才添加标题到数据库
if (responseMsg.equals(name+"附件上传成功!公告发布完成。")){
上传文件!!!因为是for遍历所以[i]使用索引获取
Timestamp dateTime = DateConverter.getDate();
当你的文件上传成功,获取当前时间这里我封装了个类,就不贴出来了。大家可以自己写获取时间这个代码。怎么写都可以你只要拿到时间就可以了!!!
message.setNotice_time(dateTime);
因为我的公告实体类中也有公告的发布时间所以set进去下面会有执行sql的代码,也就是我的业务逻辑中的发布消息公告的时间。
if(flag){
//添加方法执行sql
announceService.insertTitleAndAnnounce(message);
//将flag变为flase
flag = false;
}
当发布过一次公告就不向通知表中添加数据因为一个公告对应多个上传的附件,属于一对多。
FileTemp fileTemp = new FileTemp();
fileTemp.setDir_path(upload_folder);
fileTemp.setFile_name(name);
fileTemp.setFile_newName(newName);
fileTemp.setUploadTime(dateTime);
创建路径对象并set,添加路径、文件名、新的文件名、上传时间,下面会有insert的方法,将它们都添加到DB。
Integer id = message.getId();
fileTemp.setFid(id);
从公告实体类中拿到消息信息的id因为我设了外键关联,我的项目中有这个需求!如果你没有这个需求将非文件表的代码删除即可
。
如何获取它的自增sql我也给大家 ,这个是获取最新的insert语句的自增id值:
<!--发布公告添加标题并查询最新自增的id--> 这个要写在Mapper.xml中的sql
<insert id="insertTitleAndAnnounce" parameterType="Message">
<!--通过mybatis框架提供的selectKey标签获得自增产生的ID值-->
/*order="AFTER",声明当前select语句在insert后面执行*/
<selectKey resultType="int" keyColumn="id" order="AFTER" keyProperty="id">
select LAST_INSERT_ID()
</selectKey>
insert into message(about,notice_content,notice_time)values(#{about},#{notice_content},#{notice_time});
</insert>
fileTempService.addFileTemp(fileTemp);
执行添加文件路径到数据库的Service方法,因为fileTemp我们创建了对象并Set了值,直接将它丢进去就可以了。
三、用到的效验Excel的工具类 :
/**
* @author 码不多
* @version 1.0
* @description: excle文件格式验证工具类
* @date 2021/8/18 10:00
*/
public class ExcelValidatorUtil {
/**
* 功能描述: 是否是03版本的excel,返回true是2003
* @author 码不多
* @date 2021/8/18
* @param filePath
* @return boolean
*/
public static boolean isExcel2003(String filePath) {
//03版本为xls结尾
return filePath.matches("^.+\\.(?i)(xls)$");
}
/**
* 功能描述: 是否是07的excel,返回true是07
* @author 码不多
* @date 2021/8/18
* @param filePath
* @return boolean
*/
public static boolean isExcel2007(String filePath) {
//07版本为xlsx结尾
return filePath.matches("^.+\\.(?i)(xlsx)$");
}
/**
* 功能描述: 验证是否是EXCEL文件
* @author 码不多
* @date 2021/8/18
* @param fileName
* @return boolean
*/
public static boolean validateExcel(String fileName){
//判断excel文件,如果不是方法false
if (fileName == null || !(isExcel2003(fileName) || isExcel2007(fileName))){
return false;
}
return true;
}
}
总结 :
至此文件上传部分后端代码就完成了,如果大家有任何不懂的地方,都可以评论说出来,我会为大家逐一解答!