一、背景
目标效果是想导入 Excel 文档,后台接收并读取数据,再将数据传入到数据库中进行查询,得到结果后导出。
二、实现过程
版本一:
版本一和版本二的后台的代码都一样,所以这里主要讲解前端的处理,后端的参考版本二的介绍即可。
首先在 form 表单中放置一个 type=‘file’ 的 input 标签
<form id="formFile" method="post" action="##"
class="report_form" target="_blank">
<div class="easyui-panel" title="Upload File"
style="width: 450px; padding: 30px 70px 50px 70px">
<div style="margin-bottom: 20px">
<div>物料代码文件:</div>
<input class="import-btn" type="file" accept=".xls,.xlsx">
</div>
</div>
</form>
当选择文件发生变化时触发 Ajax 提交。简洁,就是页面不是很好看。
<script type="text/javascript">
$(function () {
$('.import-btn').change(function () {
var formData = new FormData(),
name = $(this).val()
formData.append('file', $(this)[0].files[0])
// 此处可传入多个参数
formData.append('name', name)
console.log(name+', '+$(this)[0].files[0])
$.ajax({
url: "${ctx}/bom/getLatestOrder.do",
type: 'post',
async: false,
data: formData,
// 告诉jQuery不要去处理发送的数据
processData: false,
// 告诉jQuery不要去设置Content-Type请求头
contentType: false,
beforeSend: function () {
console.log('正在进行,请稍候')
},
success: function (data) {
if (data.message != null) {
$.messager.alert("提示",data.message);
}
if (data.infos != null){
location.href = "${ctx }/bom/messageExport.do";
}
}
})
})
});
</script>
版本二:
我想用 EasyUI 的 filebox 来对文件框进行美化,所以做如下修改:
<form id="formFile" method="post" action="##" class="report_form" target="_blank">
<div class="easyui-panel" title="Upload File"
style="width: 450px; padding: 30px 70px 50px 70px">
<div style="margin-bottom: 20px">
<div>物料代码文件:</div>
<input class="easyui-filebox" id="fb" name="fb" type="text"
style="width: 300px; height: 32px;">
</div>
<div>
<a href="javascript:void(0)" class="easyui-linkbutton" style="width: 98%"
οnclick="submitForm()" id="submit">提交</a>
</div>
</div>
</form>
改了一下,选择文件变化后是先去进行判断筛选,提交按钮才是触发与后台的交互。
<script type="text/javascript">
$(function () {
//添加对话框,上传控件初始化
$('#fb').filebox({
buttonText: '选择文件', //按钮文本
buttonAlign: 'right', //按钮对齐
//multiple: true, //是否多文件方式
accept: '.xls,.xlsx', //指定文件类型
onChange: function (e) {
change(this);//上传处理
}
});
});
function change(_obj){
var tempFile = $("#fb");
var value=tempFile.filebox('getValue');
// 取后缀名
var ext=value.substring(value.lastIndexOf(".")+1).toLowerCase();
if(ext==''){ // 未选择文件,不作处理
return;
}else if((ext!='xls') && (ext!='xlsx')){
$.messager.alert("消息提示", "文件格式需为*.xls或*.xlsx 类型", "warning");
$('#fb').filebox('setValue',''); // 清空filebox回显框
return;
}
}
function submitForm(){
var fd = new FormData();
var file = document.getElementsByName("fb")[0].files[0];
if (file === undefined) {
$.messager.alert("提示","文件数据为空请正确上传文件!");
return;
}
//上传的参数名 参数值 k-v键值对
fd.append("file", file);
$.ajax({
url:"${ctx}/bom/getLatestOrder.do",
type:"post",
data:fd,
async : true, //此处为异步加载
processData: false, // 告诉jQuery不要去处理发送的数据
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
beforeSend: function () {
$("#submit").attr({ disabled: "disabled" });// 禁用按钮防止重复提交
$.messager.progress({
title: '提示',
msg: '文件处理中,请稍候……',
text: ''
});
},
success:function(data){
if (data.message != null) {
$.messager.alert("提示",data.message);
}
if (data.infos != null){
location.href = "${ctx }/bom/messageExport.do";
}
},
complete: function () {
$.messager.progress('close');
$("#submit").removeAttr("disabled");
},
error:function(e){
$.messager.alert("错误","服务器异常,请稍后重试!!!<br/>"+e.message);
},
});
}
</script>
代码我给了许多注释,我这就大致讲解一下。首先文本框的样式 class 设置为 filebox ,type 的值也改成 text ,并且是在 JS 中去设置选择文件的按钮样式,指定选择文件类型以及选择文件变化之后的判断。
接着就是提交按钮,设置了 submitForm()方法,在方法中获取 file 文件,并通过 Ajax 传送到后台进行处理。 需要注意的是,在未换成 filebox 之前,使用 JQuery【 如:$("#fb").get(0).files[0] 】去获取文件是完全没有问题的,但一使用 filebox,就不能这样去获取文件了,会报如下错误
Uncaught TypeError: Cannot read property '0' of null
所以只能像我上面的代码那样,使用原生的方法去获取【如:document.getElementsByName("fb")[0].files[0]; 】
知识点:
---------------------------------------
1、遇到的第一个问题,就是用 Ajax 提交,前后台都没有报错,但就是不会弹出下载框。原因就是导出excel,实际上是文件下载,后台需要往前端(浏览器)写文件流的。而Ajax请求获取数据都是“字符串”,整个交互传输用的都是字符串数据,它没法解析后台返回的文件流,但浏览器可以。
因此,便有了上面的解决办法,在后台我将数据处理后存储在一个全局变量里,等 Ajax 请求成功后,再用 window.location.href = "" 去处理导出的操作,这样子浏览器就可以弹出下载框啦,下面呈上 Service 实现层的代码:
package com.gangdian.qc.service.impl;
@Service
public class LatestOrderServiceImpl implements LatestOrderService {
@Autowired
private LatestOrderDao latestOrderDao;
List<LatestOrderInfos> exportMessage = new ArrayList<LatestOrderInfos>(); // 需要导出的信息
@Override
public Map<String, Object> getLatestOrder(MultipartFile file, HttpServletResponse response,
HttpServletRequest request) {
Map<String, Object> result = new HashMap<String, Object>(); // 返回的结果
String dealResult = null;
try {
InputStream inputStream = file.getInputStream(); // 获取文件的输入流
Integer length = file.getOriginalFilename().split("\\.").length; // 获取文件名数组的长度
String suffix = file.getOriginalFilename().split("\\.")[length - 1]; // 取得文件后缀名
if ("xls".equals(suffix)) {
dealResult = hssfDeal(inputStream, result, response, request); // 如果后缀为.xls则用HSSFWorkbook
result.put("message", dealResult);
} else if ("xlsx".equals(suffix)) {
dealResult = xssfDeal(file, result, response, request); // 后缀为.xlsx则用XSSFWorkbook
result.put("message", dealResult);
}
} catch (Exception e) {
result.put("message", "500!");
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* *.xlsx文件用XSSFWorkbook处理
*
* @return
*/
private String xssfDeal(MultipartFile file, Map<String, Object> result, HttpServletResponse response,
HttpServletRequest request) throws IOException {
String dealResult = null; // 返回的处理结果
// 创建workbook对象,读取整个文档
XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
Sheet sheet = workbook.getSheetAt(0);
// 获取sheet的最大row下标
int lastRowNum = sheet.getLastRowNum();
// 判断第一列标题是否包含 “物料代码”
String title = sheet.getRow(0).getCell(0).getStringCellValue();
if (title.indexOf("物料代码") < 0) {
return "请注意,首列标题须为物料代码!";
} else if (lastRowNum <= 0) {
return "您导入的excel文件为空,请重新导入!";
}
for (Row row : sheet) {
// 读取每一行的单元格
Cell cell = row.getCell(0);
if (cell != null) { // 单元格不为空
cell.setCellType(Cell.CELL_TYPE_STRING); // 将该列单元格类型设置为String
String materialCode = cell.getStringCellValue(); // 第一列数据
if (materialCode != "" && materialCode.indexOf("物料代码") < 0) {
// 获取最新使用订单情况
LatestOrderInfos latestOrderInfos = latestOrderDao.getLatestOrderInfos(materialCode);
exportMessage.add(latestOrderInfos);
}
}
}
result.put("infos", exportMessage);
return dealResult;
}
/**
* *.xls文件用HSSFWorkbook处理
*
* @return
*/
private String hssfDeal(InputStream inputStream, Map<String, Object> result, HttpServletResponse response,
HttpServletRequest request) throws IOException {
String dealResult = null; // 返回的处理结果
// 创建workbook对象,读取整个文档
POIFSFileSystem poifsFileSystem = new POIFSFileSystem(inputStream);
HSSFWorkbook wb = new HSSFWorkbook(poifsFileSystem);
// 读取页脚sheet
HSSFSheet sheetAt = wb.getSheetAt(0);
// 判断第一列标题是否包含 “物料代码”
String title = sheetAt.getRow(0).getCell(0).getStringCellValue();
if (title.indexOf("物料代码") < 0) {
return "首列标题须为物料代码!";
}
// 循环读取某一行
for (Row row : sheetAt) {
// 读取每一行的单元格
Cell cell = row.getCell(0);
if (cell != null) { // 单元格不为空
String materialCode = cell.getStringCellValue(); // 第一列数据
if (materialCode != "" && materialCode.indexOf("物料代码") < 0) {
// 获取最新使用订单情况
LatestOrderInfos latestOrderInfos = latestOrderDao.getLatestOrderInfos(materialCode);
exportMessage.add(latestOrderInfos);
}
}
}
result.put("infos", exportMessage);
return dealResult;
}
}
2、当我导入的文件格式为 *.xlsx ,却用 HSSFWorkbook 去解析的时候,出现如下的错误
org.apache.poi.poifs.filesystem.OfficeXmlFileException: The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2 Office Documents. You need to call a different part of POI to process this data (eg XSSF instead of HSSF)
直译:文件中的数据是用Office2007+XML保存的,而现在却调用OLE2 Office文档处理,应该使用POI不同的部分来处理这些数据,比如使用XSSF来代替HSSF
因此,在这里,我们需要进行区分,*.xls 用 HSSFWorkbook 进行处理,而 *.xlsx 则用 XSSFWorkbook 处理,处理的前提需要导入以下的 jar 包。网上都有,懒得找可以问我要,我再附上链接,3.8-3.10版本的我都有。
3、在读取 Excel 中的数据时,报错 java.lang.IllegalStateException: Cannot get a text value from a numeric cell
大致意思就是不能从一个数值的列获取一个字符串类型的值。在代码中,我是通过 cell.getStringCellValue() 去获取单元格数据的,POI会判断单元格的类型,如果非字符串类型就会抛出上面的异常。
解决方法:一个个的去 Excel 中将数值的单元格前面加上 ‘ ,变成字符串类型 ?这种方法太扯淡了。我们来看看比较便捷的,就是在获取值前加上以下语句,让服务器去帮你处理就可以啦,完美解决。
cell.setCellType(Cell.CELL_TYPE_STRING); // 将该列单元格类型设置为String
---------------------------------------
最后放上导出 Excel 的代码:
@Override
public void messageExport(HttpServletResponse response, HttpServletRequest request) {
// 生成表格标题行
String[] headers = { "物料代码", "最新使用单号", "最新出库日期", "最新入库日期" };
// -----设置导出表格-----
// 声明一个工作薄
HSSFWorkbook workbook = new HSSFWorkbook();
// 生成一个表格
HSSFSheet sheet = workbook.createSheet("物料最新使用订单情况");
// 设置表格默认列宽度为15个字节
sheet.setDefaultColumnWidth(15);
sheet.autoSizeColumn(1, true);
// 生成一个样式
HSSFCellStyle style = workbook.createCellStyle();
// 设置这些样式
style.setAlignment(HSSFCellStyle.ALIGN_LEFT);
style.setVerticalAlignment(org.apache.poi.ss.usermodel.CellStyle.VERTICAL_CENTER);
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style.setBorderRight(HSSFCellStyle.BORDER_THIN);
style.setBorderTop(HSSFCellStyle.BORDER_THIN);
// 生成一个字体
HSSFFont font = workbook.createFont();
font.setFontHeightInPoints((short) 12);
font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
// 把字体应用到当前的样式
style.setFont(font);
// 生成并设置另一个样式
HSSFCellStyle style2 = workbook.createCellStyle();
style2.setBorderBottom(HSSFCellStyle.BORDER_THIN);
style2.setBorderLeft(HSSFCellStyle.BORDER_THIN);
style2.setBorderRight(HSSFCellStyle.BORDER_THIN);
style2.setBorderTop(HSSFCellStyle.BORDER_THIN);
// 生成另一个字体
HSSFFont font2 = workbook.createFont();
font2.setFontName("宋体");
// 把字体应用到当前的样式
style2.setFont(font2);
// 产生表格标题行
HSSFRow row = sheet.createRow(0);
row.setHeightInPoints(30);
for (int k = 0; k < headers.length; k++) {
HSSFCell cell = row.createCell(k);
cell.setCellStyle(style);
HSSFRichTextString text = new HSSFRichTextString(headers[k]);
cell.setCellValue(text);
}
// 写入数据,并设置样式
for (LatestOrderInfos latestOrderInfos : exportMessage) {
int lastRowNum = sheet.getLastRowNum() + 1;// 当前需要增加行的下标
row = sheet.createRow(lastRowNum);
row.setHeightInPoints(20);
row.createCell(0).setCellValue(latestOrderInfos.getMaterialCode());
row.createCell(1).setCellValue(latestOrderInfos.getLatestOrder());
row.createCell(2).setCellValue(latestOrderInfos.getLatestOutDate());
row.createCell(3).setCellValue(latestOrderInfos.getLatestInDate());
for (int j = 0; j < headers.length; j++) {
row.getCell(j).setCellStyle(style2);
}
}
// 创建response输出流
SimpleDateFormat df = new SimpleDateFormat("yyMMdd-HH.mm");// 设置日期格式
String time = df.format(new Date());// new Date()为获取当前系统时间
OutputStream os = null;
try {
os = response.getOutputStream();
} catch (IOException e1) {
e1.printStackTrace();
}
response.reset();
String filename = "物料最新订单使用情况." + time + ".xls";
response.setContentType("application/msexcel");
try {
// 设定输出文件头
response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
// 定义输出类型
response.setContentType("application/msexcel");
// 写入文件
workbook.write(os);
exportMessage.clear();
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
三、知识拓展
当需要传一个很大数据到后台(window.location.href= actionUrl + "?" + data;),就会出问题了,当然,当数据量不大的时候,使用这种方式是极好的。导致这个问题的原因是各种浏览器对URL解析的长度限制是不同的。
- Microsoft Internet Explorer (Browser):IE浏览器对URL的最大限制为2083个字符,如果超过这个数字,提交按钮没有任何反应。
- Firefox (Browser):对于Firefox浏览器URL的长度限制为65,536个字符
- Safari (Browser):URL最大长度限制为 80,000个字符。
- Opera (Browser):URL最大长度限制为190,000个字符。
- Google (chrome):url最大长度限制为8182个字符
解决方法就换成了使用隐藏form来执行导出操作,如下:
<a href="javascript:void(0)" οnclick="exportExcel()">导出1</a>
// 导出,使用这种方式 可以,使用 ajax请求不可以 导出excel
function exportExcel(){
var form = $("<form>");
form.attr('style', 'display:none');
form.attr('target', '');
form.attr('method', 'post');
form.attr('action', '${pageContext.request.contextPath}/user/export');
var input1 = $('<input>');
input1.attr('type', 'hidden');
input1.attr('name', 'item');
input1.attr('value', 'test'); /* JSON.stringify($.serializeObject($('#searchForm'))) */
$('body').append(form);
form.append(input1);
form.submit();
form.remove();
}
我还没试过这种方法,先拿小本本记下来,来源:https://www.cnblogs.com/skytoangel/p/11276233.html