Servlet解析Excel文件的两种方式
简单分享一下Servlet通过解析Excel文件得到其中数据的两种方式
第一种:前端获取
思路:通过layui的第三方插件 layui.excel 解析excel文件,得到数据,再通过Ajax传递给后端,后端只需要接受参数并封装到实体类中即可。以下是具体步骤:
1、准备环境
搭建Web项目,导入相关jar包,下载插件等;具体略
2、编写JSP文件
<%
String path = request.getContextPath();
%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
......
<%--引入layui静态资源文件--%>
<link rel="stylesheet" href="<%=path%>/layui/css/layui.css" type="text/css">
</head>
<body>
<div class="layui-upload">
<button type="button" class="layui-btn" id="uploadExcelOne">上传Excel</button>
</div>
<%--引入静态资源文件--%>
<script src="<%=path%>/layui/layui.js" type="text/javascript"></script>
<script src="<%=path%>/layui/jquery.js" type="text/javascript" ></script>
<script>
// 前端处理方式
layui.config({
base: '<%=path%>/layui/layui_exts/',
}).use(['jquery','layer','upload','excel'], function () {
$ = layui.jquery;
var excel = layui.excel;
var upload = layui.upload;
var layer = layui.layer;
upload.render({
elem: '#uploadExcelOne',
// 不自动上传
auto: false,
accept: 'file',
// 打开文件选中框时只显示Excel格式的文件
acceptMime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel',
// 规定上传的格式
exts: 'xls|excel|xlsx',
choose: function (obj) {
var files = obj.pushFile();
var fileArray = Object.values(files);
// 用完就清理掉,避免多次选中相同文件时出现问题
for (var index in files) {
if (files.hasOwnProperty(index)) {
delete files[index]
}
}
uploadExcel(fileArray);
}
});
// 上传文件的具体方法
function uploadExcel(files) {
try {
excel.importExcel(files, {
// 读取数据的同时梳理数据 可参考官方文档
fields: {
'id': 'A',
'name': 'B',
'age': 'C',
'height': 'D',
'birthday': 'E'
}
}, function (data) {
$.each(data, function (index, obj) {
$.each(obj.Sheet1, function (index, object){
var id = object.id;
var name = object.name;
var age = object.age;
var height = object.height;
// Excel文件中时间比较特殊,需做如下处理
var birthday = LAY_EXCEL.dateCodeFormat(object.birthday, 'YYYY/MM/DD');
if(id === "id") {
console.log("读取了表头");
} else {
$.ajax({
url: "<%=path%>/byb/test?action=updateExcelOne",
dataType: "json",
type: 'get',
contentType: "application/json;charset=utf-8",
data: {
id: id,
name: name,
age: age,
height: height,
birthday: birthday,
},
success: function (result) {
if (result.status === 200) {
layer.msg('上传成功');
} else {
layer.msg('上传失败');
}
},
error: function () {
layer.msg('请联系管理员!!!');
}
});
}
})
});
})
} catch (e) {
layer.alert("未知错误,请稍后再试...");
}
}
});
</script>
</body>
</html>
前端处理完成后就可以让后台接收了。
3、Servlet接收
package upload;
@WebServlet("/byb/test")
@MultipartConfig
public class FileUploadTest extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, ParseException {
// 获取请求参数
String action = request.getParameter("action");
action = action == null ? "" : action;
// 定义返回map
Map<String, Object> resultMap = new HashMap<>();
switch (action) {
case "updateExcelOne":
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
// 获取参数并封装到实体类 这里可以根据Excel的字段创建一个对应的实体类
Person person = new Person();
person.setId(Integer.parseInt(request.getParameter("id")));
person.setName(request.getParameter("name"));
person.setAge(Integer.parseInt(request.getParameter("age")));
person.setHeight(Double.parseDouble(request.getParameter("height")));
person.setBirthday(sdf.parse(request.getParameter("birthday")));
try {
// 没有顺序
System.out.println(person);
// 得到实体类之后就可以实现具体业务了 略...
resultMap.put("status", 200);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("status", 201);
}
response.getWriter().println(JSONArray.toJSONString(resultMap));
response.getWriter().flush();
response.getWriter().close();
break;
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
processRequest(req, resp);
} catch (ParseException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
processRequest(req, resp);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
4、运行测试
5、总结
缺点:
1.Excel中的数据顺序被打乱,例如Excel中数据的顺序是1-10,传到后台顺序就完全乱了
2.一条数据发送一个Ajax请求,那如果Excel中有上万条数据,是不是就会影响到性能呢?
3.前端解析暂不支持IE11及以下版本,其他浏览器支持
第二种:后端获取
思路:将整个Excel文件提交给后端,后端通过相关工具类解析文件并获取到相关数据;以下是具体步骤:
1、准备环境
在上面的环境中添加即可
2、编写JSP文件
<div class="layui-upload">
<button type="button" class="layui-btn" id="uploadExcelTwo">上传Excel后端取值</button>
</div>
<script>
// 在原先的JSP文件中添加
layui.use(["layer","upload"], function () {
$ = layui.jquery;
var upload = layui.upload;
upload.render({
elem: '#uploadExcelTwo',
url: '<%=path%>/byb/test?action=updateExcelTwo',
//选择文件后是否自动上传
auto: true,
// 指定普通文件
accept: 'file',
// 弹出文件显示框时只展示Excel类型的文件
acceptMime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel',
// 规定上传的格式
exts: 'xls|excel|xlsx',
// 禁止多文件上传
multiple: false,
// 是否开启拖拽文件上传 默认为true
drag: true,
done: function (result) {
if (result.status === 200) {
layer.alert("已成功获取到Excel数据", {icon: 6});
} else if (result.status === 202) {
layer.alert("Excel文件内容为空", {icon: 5});
} else {
layer.alert("Excel数据错误,请修改并重新导入", {icon: 5});
}
},
error: function () {
layer.alert("未知错误,请联系管理员", {icon: 5});
}
});
});
</script>
3、实体类
根据Excel中的字段编写一个对应的实体类:比如Excel文件如下(Excel第一行是有表头的):
id |
name |
age |
height(double类型) |
birthday(日期类型) |
1 |
张三1 |
18 |
10.10 |
2020/10/10 |
2 |
张三2 |
19 |
11.10 |
2020/10/11 |
… |
… |
… |
… |
… |
则实体类:
@Date
public class Person {
private Integer id;
private String name;
private Integer age;
private Double height;
private Date birthday;
}
4、编写Excel解析工具类
package upload;
public class UploadExcelUtil {
// 总行数
private int totalRows = 0;
// 总列数
private int totalCells = 0;
// 错误信息
private String errorMsg;
// 构造方法
public UploadExcelUtil() {
}
public int getTotalRows() {
return totalRows;
}
public int getTotalCells() {
return totalCells;
}
public String getErrorMsg() {
return errorMsg;
}
/**
* 根据流以及文件名得到Excel文件里的所有数据 封装到实体类中并返回集合
*/
public List<Person> getExcelInfo(InputStream inputStream, String fileName) throws FileNotFoundException {
List<Person> list = null;
try {
// 验证文件名是否合格
if (!validateExcel(fileName)) {
return null;
}
// 根据文件名判断文件是2003版本还是2007版本
boolean isExcel2003 = true;
if (isExcel2007(fileName)) {
isExcel2003 = false;
}
list = createExcel(inputStream, isExcel2003);
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
/**
* 根据excel里面的内容读取信息
* @param is 输入流
* @param isExcel2003 excel是2003还是2007版本
*/
public List<Person> createExcel(InputStream is, boolean isExcel2003) {
List<Person> list = null;
try {
Workbook wb;
if (isExcel2003) {
// 当excel是2003时,创建excel2003
wb = new HSSFWorkbook(is);
} else {
// 当excel是2007时,创建excel2007
wb = new XSSFWorkbook(is);
}
// 读取Excel里面客户的信息
list = readExcelValue(wb);
} catch (IOException | ParseException e) {
e.printStackTrace();
}
return list;
}
/**
* 读取Excel里面的信息
*/
private List<Person> readExcelValue(Workbook wb) throws ParseException {
List<Person> list = new ArrayList<>();
// 得到第一个shell
Sheet sheet = wb.getSheetAt(0);
// 得到Excel的行数
this.totalRows = sheet.getPhysicalNumberOfRows();
// 得到Excel的列数(前提是有行数)
if (totalRows > 1 && sheet.getRow(0) != null) {
this.totalCells = sheet.getRow(0).getPhysicalNumberOfCells();
}
// 循环Excel行数 r=1表示从第二行开始读 第一行是表头
for (int r = 1; r < totalRows; r++) {
Row row = sheet.getRow(r);
if (row == null) {
// 如果该行为空则结束本次循环开始读下一行
continue;
}
// 一行数据封装为一个新对象
Person person = new Person();
// 循环Excel的列
for (int c = 0; c < totalCells; c++) {
Cell cell = row.getCell(c);
if (null != cell) {
switch (c) {
case 0:
// 第一列是id
cell.setCellType(Cell.CELL_TYPE_STRING);
String id = String.valueOf(cell.getStringCellValue());
person.setId(Integer.parseInt(id));
break;
case 1:
// 第2列是名字
cell.setCellType(Cell.CELL_TYPE_STRING);
String name = String.valueOf(cell.getStringCellValue());
person.setName(name);
break;
case 2 :
// 第3列是年龄
cell.setCellType(Cell.CELL_TYPE_STRING);
String age = String.valueOf(cell.getStringCellValue());
person.setAge(Integer.parseInt(age));
break;
case 3 :
// 第4列是身高 double类型数据
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
person.setHeight(cell.getNumericCellValue());
break;
case 4 :
// 第5列是生日 date类型数据
if (cell.getCellType() == 0) {
if (HSSFDateUtil.isCellDateFormatted(cell)){
Date date = cell.getDateCellValue();
person.setBirthday(date);
} else {
// 如果格式不正确就设置为空
person.setBirthday(null);
}
}
break;
default :
break;
}
}
}
list.add(person);
}
return list;
}
/**
* 验证EXCEL文件
*/
public boolean validateExcel(String filePath) {
if (filePath == null || !(isExcel2003(filePath) || isExcel2007(filePath))) {
errorMsg = "文件名不是excel格式";
return false;
}
return true;
}
// 是否是2003的excel,返回true是2003
public static boolean isExcel2003(String filePath) {
return filePath.matches("^.+\\.(?i)(xls)$");
}
// 是否是2007的excel,返回true是2007
public static boolean isExcel2007(String filePath) {
return filePath.matches("^.+\\.(?i)(xlsx)$");
}
}
5、Servlet接收
在上面的Servlet类中添加如下:
case "updateExcelTwo":
// 获取上传的文件
Collection<Part> parts01 = request.getParts();
String fileName01 = null;
try {
for (Part part : parts01) {
if(part.getHeader("Content-Disposition").contains("filename")) {
// 上传文件的头部信息
String fileHeader = part.getHeader("Content-Disposition");
// 获取到上传文件的文件名
fileName01 = fileHeader.substring(fileHeader.indexOf("filename=\"") + 10, fileHeader.lastIndexOf("\""));
// 获取文件的输入流
InputStream in = part.getInputStream();
// 处理Excel的工具
UploadExcelUtil uploadExcel = new UploadExcelUtil();
// 使用工具类处理后得到数据集合
List<Person> list = uploadExcel.getExcelInfo(in, fileName01);
if (list.size() == 0) {
// Excel没有读取到数据
resultMap.put("status", 202);
break;
} else {
for (Person person : list) {
// 得到每一个实体类就可以实现具体业务了 例如批量更新数据 具体略..
System.out.println(person);
}
resultMap.put("status", 200);
}
}
}
} catch (Exception e) {
e.printStackTrace();
resultMap.put("status", 201);
}
// 转换为json返回前台
response.getWriter().println(JSONArray.toJSONString(resultMap));
response.getWriter().flush();
response.getWriter().close();
break;
6、运行测试
7、总结
根据效果图可看到数据顺序没有被打乱,而且这种后台处理数据的方式明显会更加高效;个人建议使用第二种方式。