前端导入Excel,后台接收并处理(超详细!)

一、背景

    目标效果是想导入 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

发布了82 篇原创文章 · 获赞 9 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Alone_in_/article/details/104949172
今日推荐