layui+poi-Java实现导入导出excel文件

目录


需求说明

项目背景:springmvc架构下,前端jsp(引入layui框架样式)需要实现导出excel文件(模板),用户填写内容后,再上传进系统,系统解析保存excel文件中的数据。


一、实现思路

1、【模板制作】提前做好一个excel文件,可以用Excel自带的功能限制单元格的格式:例如某列只允许填写纯数字,某列不允许为空等等,以便用户填写错误。

2、【导出模板】把模板放到项目中,前端请求后,后端直接下载模板。

3、【导入】使用layui框架实现前端,java后端读取excel文件,POI框架操作文件,验证数据正确性等。

二、前端代码

1.引入layui

<script src="${xxxx}/layui-v2.4.5/layui/layui.all.js" charset="utf-8"></script>

2.隐藏部分内容

有一弹窗,点击【导入xx】按钮之前不显示。点击【导入xx】按钮后弹出:

1)静态页面代码:

<body>
	<div style="display: none;padding: 5px" id="showUploadExcelDiv">
			<form action="" method="post" class="layui-form layui-form-pane" id="exportFrm" lay-filter="exportFrm">
				<div class="layui-upload" >
					<button type="button" style="margin-top: 20px;margin-left: 20px" class="layui-btn layui-btn-warm" 
						 onclick="downloadTemplate()">下载模板</button>
					<button type="button" style="margin-top: 20px;margin-left: 20px" class="layui-btn layui-btn-normal" 
						id="chooseFile">选择文件</button>
					<button type="button" style="margin-top: 20px" class="layui-btn"
						id="uploadFle">开始上传</button>
				</div>
			</form>
		</div>
		<table class="layui-hide" id="test" lay-filter="test"></table>
	<form>
		<div class="panel-body">
			<div class="row-fluid">
				<div class="span3">	
					<span class="label-none"> <br/></span>
					<button type="button" id="uploadFile" class="layui-btn layui-btn-sm"   onclick="upload();" >导入</button>
				</div> 
			</div>
		</div>
    </form>
<body>

2)js(/jquery)代码:点击【导入xx】按钮的js:弹出上面隐藏的内容

        function upload(){
			 layer.open({
	                type: 1,
	                area: ['800px','150px'],
	                fixed: false,
	                title: '导入数据',
	                content: $("#showUploadExcelDiv"),
	                end: function () {
	                    location.reload();
	                }
	            })
		}

3)、效果如下

 

3、下载模板js

function downloadTemplate(){
    //后端下载地址
    location.href="${xxx}/xxx/yyy/downloadTemplate";
}

4、选择文件、上传js 

	<script>
	/* 导入 */
	layui.use(['form', 'layedit','upload'], function(){
	    var form = layui.form,
	    $ = layui.jquery,
	    layer = layui.layer,
	    layedit = layui.layedit,
	    upload=layui.upload;

	    upload.render({
            elem: "#chooseFile",   //导入id
            //这里可以带一些页面上想传给后台的参数
            data:{
                id:function(){
                	var id = $("#id").val();
                    return id;
                },
                year:function(){
                	var year = $("#year").val();
                    return year;
                }
            },
            url: '${xx}/xxx/yyy/importExcel?year='+year+"&id="+id,
            // 与bindAction一起使用,不直接上传,当点击#uploadFle按钮时才上传
            auto: false,    
            bindAction: '#uploadFle',
            accept: "file",
            size: 10240,
            exts: 'xls|xlsx', 
            /* 上传完毕回调 */
            done: function (res) {
                /* 后端传code值:1代表成功,0代表失败 */
            	var code = res.code;
            	if(code == "1"){
            		top.$.jBox.tip("导入成功!");
            		//可以跳转到某页面
            		//location.href="${xxx}/xxx/yyy/form;
                    //可以刷新本页面
            		//window.location.reload()
            	}else{
                    //失败后弹出页面,上面附有失败原因(后端写好传来)
            		layer.confirm(res.msg, {title:'导入失败,系统提示'},function(index){
            			layer.close(index);
            			window.location.reload()
            		});
            	}
            },
            error: function(){
                //请求异常回调
                top.$.jBox.tip("服务器异常,请稍后再试");
                window.location.reload()
            }
        });
	 })
</script>

效果展示:


 三、后端代码

1、下载模板:


    /**
	 * 文件模板下载
	 * @param fileModleId
	 * @param request
	 * @param response
	 * @throws Exception
	 */
	@RequestMapping(value = "downloadTemplate")
	public void downloadTemplate(HttpServletResponse response) throws Exception {
        //获取项目中模板为输入流
		InputStream is=Thread.currentThread().getContextClassLoader().getResourceAsStream("templates/xxx/yyyy/DetailTemp.xlsx");
        //利用poi框架导出文件
        //org.apache.poi.xssf.usermodel.XSSFWorkbook;
		XSSFWorkbook wb0 = new XSSFWorkbook(is);
		try {
			OutputStream out = response.getOutputStream();
			response.setHeader("Content-disposition", "attachment;filename="+ new String( "导入成本明细清单模板.xlsx".getBytes("gb2312"), "ISO8859-1" ));
			response.setContentType("application/msexcel;charset=UTF-8");
			wb0.write(out);
			out.flush();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

2、导入文件 

1)controller层

	/**
	 * 导入excel文件
	 * @param file
	 * @return
	 */
	@RequestMapping(value = "importExcel", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ResponseBody
    public HashMap<String, Object> importData(@RequestPart(value = "file") MultipartFile file,HttpServletRequest request) {
		
		//获取导入时传来的参数:可做业务处理:略
		String year = request.getParameter("year");
		String id = request.getParameter("id");
		
        //构建返回前端值,此处使用HashMap<String, Object>
        //其中key为code value为1代表成功,value为0代表失败
        //   key为msg,value写入具体错误信息是什么
		HashMap<String, Object> resultMap = null;
		try {
    		InputStream inputStream = file.getInputStream();
            //写个工具类,处理excel 的校验、保存:返回值直接是可以对接前端的
            resultMap = util.readExcel(inputStream, file.getOriginalFilename(),new XXXEntity());
            inputStream.close();
            return resultMap;
		} catch (Exception e) {
			System.out.print("[导入异常]"+e.getMessage());
			resultMap.put("code", "0");
			resultMap.put("msg", "读取文件异常!");
			return resultMap;
	    }
    }

2)处理文件工具类

@Service
public class ExcelUtilForXXXImport {
	private static final Logger logger = LoggerFactory.getLogger(ExcelUtilForXXXImport.class);

	@Autowired
	private XXXEntityService xxxEntityService;
	
	/**
     * 处理上传的文件
     *
     * @param in
     * @param fileName
     * @return
     * @throws Exception
     */
    public HashMap<String, Object> readExcel(InputStream in, String fileName,XXXEntity xxxEntity) throws Exception {
        //构建返回值
    	HashMap<String, Object> hashMap=new HashMap<String, Object>();
        //创建Excel工作薄
        try {
        	//判断文件格式是否为excel,是否为空
            //详见:3)后端校验文件格式的方法
        	Workbook work = getWorkbook(in, fileName);
            if (null == work) {
                throw new Exception("创建Excel工作薄为空!");
            }

            //读第一页
            Sheet sheet = work.getSheetAt(0);

            //step1: 验证导入文件的标题头是否合法
    		String[] columnName = {"序号", "月份",...};
            //详见 : 4)校验表格标题行方法
    		String resultString=verificationExcelHeadLine(sheet, columnName);
            //有返回信息代表有错误,没有返回信息代表验证通过
    		if (!resultString.equals("")) {
    			hashMap.put("code", "0");
    			hashMap.put("msg", "请使用正确模板上传excel文件。"+resultString);
    			return hashMap;
    		}

    		//step2: 验证导入文件的标题 列 是否合法
    		String[] colName = {"第一月", "第二月",。。。};
            //详见: 6)判断列标题头是否正确
    		String resultStr = verificationExcelHeadLineTwo(sheet, colName);
            //有返回信息代表有错误,没有返回信息代表验证通过
    		if (!resultStr.equals("")) {
    			hashMap.put("code", "0");
    			hashMap.put("msg", "请使用正确模板上传excel文件。"+resultStr);
    			return hashMap;
    		}

    		//step3: 验证数据是否为空,长度是否过长,是否为纯数字。。。
            // 详见7)校验数据是否为空
    		String verificationDateResultString = verificationDate(sheet);
    		if (!verificationDateResultString.equals("")) {
    			hashMap.put("code", "0");
    			hashMap.put("msg", verificationDateResultString);
    			return hashMap;
    		}

    		//step4: 文件内容合法,做业务逻辑处理(保存)
            // 详见9)做业务逻辑处理(保存数据)
    		String saveResultString = insertData(sheet,xxxEntity);
    		if (!saveResultString.equals("")) {
    			hashMap.put("code", "0");
    			hashMap.put("msg", saveResultString);
    			return hashMap;
    		}

            //以上都顺利进行,返回前端:导入成功
    		hashMap.put("code", "1");
	        hashMap.put("msg", "导入成功!");
	        return hashMap;
		} catch (Exception e) {
			System.out.print("[读取excel异常]"+e.getMessage());
			hashMap.put("code", "0");
	        hashMap.put("msg", "读取excel异常");
	        return hashMap;
		}
        
    }

}

3)后端校验文件格式的方法

	/**
     * 后端判断文件格式方式
     *
     * @param inStr
     * @param fileName
     * @return
     * @throws Exception
     */
    public static Workbook getWorkbook(InputStream inStr, String fileName) throws Exception {
        Workbook workbook = null;
        String fileType = fileName.substring(fileName.lastIndexOf("."));
        if (".xls".equals(fileType)) {
            workbook = new HSSFWorkbook(inStr);
        } else if (".xlsx".equals(fileType)) {
            workbook = new XSSFWorkbook(inStr);
        } else {
            throw new Exception("请上传excel文件!");
        }
        return workbook;
    }

4)校验表格标题行方法

    /***
     * 校验导入的列表Excel文件标题行是否为标准行
     */
    public static String verificationExcelHeadLine(Sheet sheet, String[] columnName) throws Exception {
    	String result = "";
    	try {
            //获取第一行:标题行
    		Row row = sheet.getRow(0);
            
    		if (row != null && row.getLastCellNum() >= columnName.length) {
    			int lastCellNum = row.getLastCellNum();
                //循环遍历指定的数组内容与文件中内容一一对比
    			for (int idx = 0; idx < lastCellNum; idx++) {
                    //将格子中的内容转为java的string类型的方法
                    //详见5)将excel格子中的内容转为java的string类型的方法
    				String value = getStringCellValue(row.getCell(idx));
    				if (idx < 4) {
    					if (value == null || !columnName[idx].equals(value)) {
    						result = result+"标题行第" + (idx + 1) + "列名称错误!";
    					}
    				} else {
    					if (idx == 4) {
    						if (value == null) {
    							result = result+"标题标准表格表头不一致";
    						}
    					}
    				}
    			}
    		} else {
    			result = "上传文件首行不能为空或与标准表格表头不一致!";
    		}
    	} catch (Exception ex) {
    		logger.info("【导入成本】校验数据异常");
    	}
    	return result;
    }

5)将excel格子中的内容转为java的string类型的方法 

    /**
     * 获取单元格数据内容为字符串类型的数据
     * 
     * @param cell Excel单元格
     * @return String 单元格数据内容
     */
    private static String getStringCellValue(Cell cell) {
        String strCell = "";
        switch (cell.getCellType()) {
        case Cell.CELL_TYPE_STRING:
            strCell = cell.getStringCellValue();
            break;
        case Cell.CELL_TYPE_NUMERIC:
            strCell = String.valueOf(cell.getNumericCellValue());
            break;
        case Cell.CELL_TYPE_BOOLEAN:
            strCell = String.valueOf(cell.getBooleanCellValue());
            break;
        case Cell.CELL_TYPE_BLANK:
            strCell = "";
            break;
        default:
            strCell = "";
            break;
        }
        if (strCell.equals("") || strCell == null) {
            return "";
        }
        if (cell == null) {
            return "";
        }
        return strCell;
    }
    

6)判断列标题头是否正确

    public String verificationExcelHeadLineTwo(Sheet sheet, String[] columnName) {
    	String result = "";
    	try {
    		int allRowNum = sheet.getLastRowNum();
    		for(int i = 1; i <= allRowNum; i++) {
    			Row row = sheet.getRow(i);
    			if (row != null && row.getCell(1) != null) {
    				String value = getStringCellValue(row.getCell(1));
    				if (value == null || !columnName[i-1].equals(value)) {
						result = result+"第" +i+"行第2列名称错误!";
					}
        		} else {
        			result = "请选择正确模板!";
        		}
    		}
    	} catch (Exception ex) {
    		logger.info("【导入成本】校验数据异常");
    	}
    	return result;
    }

 7)校验数据是否为空

	/**
     * 校验数据是否为纯数字,是否为空
     * @param sheet
     * @return
     */
	
	private String verificationDate(Sheet sheet) {
		String result = "";
		int allRowNum = sheet.getLastRowNum();
		if(allRowNum != 12) {
			result = "数据行数不为12行,请检查模板;";
			return result;
		}
    	try {
    		//循环行
    		for(int i = 1; i <= sheet.getLastRowNum(); i++) {
    			Row row = sheet.getRow(i);
    			int allCellNum = row.getLastCellNum();
        		if (row != null &&  allCellNum == 9) {
        			//循环列
        			for (int idx = 0; idx < allCellNum; idx++) {
        				String value = getStringCellValue(row.getCell(idx));
        				if(!StringUtils.isNotEmpty(value)) {
        					result = result+"第"+i+"行"+"第"+idx+"列为空;";
        				}else {
        					//第二列以后为纯数字
        					if(idx>=2) {
                                //isNumeric为校验是否为数字的方法
                                //详见:8)校验是否为纯数字
        						if(!isNumeric(value)) {
        							result = result+"第"+i+"行"+"第"+idx+"列不是数字格式;";
        						}
        					}
        				}
        			}
        		} else {
        			result = "数据列数不为8列,请检查模板;";
        			return result;
        		}
    		}
    	} catch (Exception ex) {
    		 logger.info("【导入成本】校验数据异常");
    	}
    	return result;
	}
	 

8)校验是否为纯数字 :心机boy的方法

    /**
     * 判断是否为数字:直接去创建BigDecimal,失败就是非数字,BigDecimal校验的比我们厉害
     * @param str
     * @return 
     */
    public static boolean isNumeric(String str) {
        String bigStr;
        try {
            bigStr = new BigDecimal(str).toString();
        } catch (Exception e) {
            return false;//异常 说明包含非数字。
        }
        return true;
    }

9)做业务逻辑处理(保存数据)



    private String insertData(Sheet sheet,XXXEntity xxxEntity) {
    	String result = "";
    	
    	//构建子表数据
    	List<XXXEntity> xxxEntityList = new ArrayList<XXXEntity>();
    	Row row = null;
    	try {
    		for (int i =1; i <= sheet.getLastRowNum(); i++) {
                row = sheet.getRow(i);
                XXXEntity entity = new XXXEntity();
                
                entity.setMonth(getStringCellValue(row.getCell(0)));
                entity.setXXX(
                    new BigDecimal(
                        getStringCellValue(row.getCell(1))));
                //...
                xxxEntityList.add(entity);
            }
   
        	xxxEntityService.save(jCCostManager);
    	}catch(Exception e) {
    		result = "数据格式异常无法保存。";
    		return result;
    	}
    	
    	return result;
    }
    

总结

        以上可以实现简单导入导出excel,如有错误,请指正!

        希望广大网友有更简便的方法或框架可以提供~

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员调优

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值