1、下载excel模板
1.1、配置后端服务的静态资源路径
在学习 SpringBoot 整合 SpringMVC 的时候,我们需要自己编写一个 配置类,来指定 SpringBoot 项目的静态资源的目录,配置类如下
package com.exam.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 开启 mvc支持,设置 static 目录为类路径
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// 得到 classpath 的根路径, resources 目录下的所以路径都可以得到
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
}
然后在浏览器,这个静态资源的请求路径就是 http://localhost:xxx/download/你的文件名称
1.3、前端vue实现下载功能
href 表示资源的路径,
download 表示指定下载的文件
<a class='download' href='api/download/student.xlsx' download="student.xlsx" title="excel模板下载" style="color: white">下载学生模板a>
el-button>
注意:
1、href=‘api/download/student.xlsx’ 中的api是我配置过的,如果没有配置,要写成 href=‘http://localhost:xxxx/download/student.xlsx’
2、download=“student.xlsx” ,中的文件名一定要和后端的文件名保持一致,否则会下载失败。
2、上传excel文件并读取实现批量添加
2.1、 SpringBoot 后端部分功能实现 —— 配置文件部分
#开启文件上传
spring.servlet.multipart.enabled=true
#文件写入磁盘的阈值
spring.servlet.multipart.file-size-threshold=3KB
spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=50MB
#自定义文件保存路径
gorit.file.root.path= e:/data/
如果不保存文件,可以不配置文件的写入、默认保存路径
2.2、添加poi的依赖
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>3.16version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.16version>
dependency>
2.3、编写controller
package com.exam.controller;
import com.alibaba.druid.util.StringUtils;
import com.exam.entity.ApiResult;
import com.exam.entity.Student;
import com.exam.service.StudentService;
import com.exam.serviceimpl.StudentServiceImpl;
import com.exam.util.ApiResultHandler;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
public class FileController {
@Autowired
private StudentServiceImpl studentService;
// 设置固定的日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 将 yml 中的自定义配置注入到这里
@Value("${gorit.file.root.path}")
private String filePath;
// 日志打印
private Logger log = LoggerFactory.getLogger("FileController");
// 文件上传 (可以多文件上传)
@PostMapping("/upload")
public ApiResult fileUploads(HttpServletRequest request, @RequestParam("file") MultipartFile file) throws IOException {
// // 得到格式化后的日期
String format = sdf.format(new Date());
// 获取上传的文件名称
String fileName = file.getOriginalFilename();
// 时间 和 日期拼接
String newFileName = format + "_" + fileName;
// 得到文件保存的位置以及新文件名
File dest = new File(filePath + newFileName);
try {
// 上传的文件被保存了
// file.transferTo(dest);
// 打印日志
// log.info("上传成功,当前上传的文件保存在 {}",filePath + newFileName);
// 读取文件,添加学生信息
if(file == null){
return ApiResultHandler.buildApiResult(400, "上传失败,文件为空!", 0);
}
//读取文件流
InputStream inputStream = file.getInputStream();
//文件名
String fileName2 =file.getOriginalFilename();
if (!fileName2.matches("^.+\\.(?i)(xls)$") && !fileName2.matches("^.+\\.(?i)(xlsx)$")) {
return ApiResultHandler.buildApiResult(400, "上传失败,文件格式不正确!", 0);
}
Workbook wb = null;
if (fileName2.matches("^.+\\.(?i)(xlsx)$")) {
//xlsx格式
wb = new XSSFWorkbook(inputStream);
} else {
//xls格式
wb = new HSSFWorkbook(inputStream);
}
if (wb != null){
//姓名 年级 性别 学号(密码)
//默认读取第一个sheet
Sheet sheet = wb.getSheetAt(0);
if (sheet != null){
//最先读取首行
boolean firstRow = true;
List<Student> studentList = new ArrayList<>();
boolean isThrow = false;
//根据学号判断文件是否包含重复的学生,
//如果需要多个字段唯一确定一条数据可以使用List
//下面同样使用contains判断是否已经包含同一条数据
List<String> phoneList=new ArrayList<>();
try {
if (sheet.getLastRowNum() > 0) {
for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) {
//循环行
Student student = new Student();
Row row = sheet.getRow(i);
//首行 提取注解
if (firstRow) {
if (row.getCell(0).getStringCellValue().equals("姓名")
&& row.getCell(1).getStringCellValue().equals("年级") //性别
&& row.getCell(2).getStringCellValue().equals("性别") //手机号
&& row.getCell(3).getStringCellValue().equals("学号"))//年级班级
{
} else {
return ApiResultHandler.buildApiResult(400, "格式不正确,请下载模板进行参考", 0);
}
firstRow = false;
} else {
//忽略空白行
// if (row == null || ToolHelp.isRowEmpty(row)) {
// continue;
// }
int theRow = i + 1;
if (row.getCell(0) != null) {
row.getCell(0).setCellType(CellType.STRING);
String stuName = row.getCell(0).getStringCellValue();
if (StringUtils.isEmpty(stuName)) {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,姓名不能为空)", 0);
} else {
student.setStudentName(stuName);
}
} else {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,姓名不能为空)", 0);
}
if (row.getCell(1) != null) {
row.getCell(1).setCellType(CellType.STRING);
String grade = row.getCell(1).getStringCellValue();
if (StringUtils.isEmpty(grade)) {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,年级不能为空)", 0);
} else {
student.setGrade(grade);
}
} else {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,年级不能为空)", 0);
}
if (row.getCell(3) != null) {
//学号为密码
row.getCell(3).setCellType(CellType.STRING);
String pwd = row.getCell(3).getStringCellValue();
if (StringUtils.isEmpty(pwd)) {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,学号有误)", 0);
} else {
if (!phoneList.isEmpty() && phoneList.size() > 0) {
//判断手机号是否重复
if (phoneList.contains(pwd)) {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + pwd + "行,学号有重复)", 0);
} else {
phoneList.add(pwd);
student.setPwd(pwd);
}
} else {
student.setPwd(pwd);
}
}
} else {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,学号有重复)", 0);
}
if (row.getCell(2) != null) {
row.getCell(2).setCellType(CellType.STRING);
String sex = row.getCell(2).getStringCellValue();
if (StringUtils.isEmpty(sex)) {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,性别不能为空)", 0);
} else {
student.setSex(sex);
}
} else {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败(第" + theRow + "行,性别不能为空)", 0);
}
//默认为男
student.setSex("男");
studentList.add(student);
}
if (isThrow) {
break;
}
}
} else {
isThrow = true;
return ApiResultHandler.buildApiResult(400, "导入失败,数据为空", 0);
}
}catch (Exception e) {
e.printStackTrace();
}
for (Student student :studentList){
studentService.add(student);
}
}
}
// 自定义返回的统一的 JSON 格式的数据,可以直接返回这个字符串也是可以的。
return ApiResultHandler.buildApiResult(200, "上传成功,添加成功!", 1);
} catch (IOException e) {
log.error(e.toString());
}
// 待完成 —— 文件类型校验工作
return ApiResultHandler.buildApiResult(400, "上传失败!", 0);
}
}
注意:
1、ApiResult是封装返回的类,换成自己的
2、根据自己excel的单元格信息,正确的给实体类(student)的属性赋值。
2、3 前端vue实现上传文件
<el-button class="btn-upload" type="success" @click="handleUpdate">上传学生信息el-button>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
>
<span>
<el-upload class="upload-demo"
ref="upload"
drag
action="/api/upload"
multiple
:auto-upload="false"
:limit="5"
:on-success="handleFilUploadSuccess"
:on-remove="handleRemove"
>
<i class="el-icon-upload">i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传em>div>
<div class="el-upload__tip" slot="tip">只能上传 Excel 文件,且不超过500kbdiv>
el-upload>
span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消el-button>
<el-button type="primary" @click="handleUpload">确 定el-button>
span>
el-dialog>
data() {
return {
dialogVisible: false,
},
methods: {
handleRemove(file,fileList) {
console.log(file,fileList);
},
submitUpload() {
this.$refs.upload.submit();
},
// 文件上传成功时的函数
handleFilUploadSuccess (res,file,fileList) {
console.log(res,file,fileList)
this.$message.success("上传成功")
},
handleUpdate () {
this.dialogVisible = true;
},
// 处理文件上传的函数
handleUpload () {
// console.log(res,file)
this.submitUpload()
this.dialogVisible = false
}
}
.btn-upload {
top: 70px;
right: 40px;
z-index: 100;
box-shadow: 0 2px 12px 0 #67c23a;
}