前言:拖更了很久,主要是公司项目太忙,抽不出大块时间去整理开发过程中遇到的问题或是心得。当然也有自己最近一段时间对于写博客有些懈怠的原因,以后尽可能的最少每天出一篇吧。
正题:相信很多小伙伴在企业级项目开发过程中,都遇到过对Office格式文档操作的问题,博主这段时间遇到这方面的需求比较多,用此文MARK一下。
简单用一下百度词条给出的 POI 释意,POI是Apache的开源函式库,提供API给JAVA程序对Office格式档案读写功能。
那我们大家立刻开始试试吧!
其一,使用POI操作Word
江湖规矩,先上POM文件
org.apache.poi poi-scratchpad 4.1.0 org.apache.poi poi-ooxml 4.1.0 org.apache.poi poi-ooxml-schemas 4.1.0 以下代码仅为测试demo,提供思路而已,请根据具体场景做对应处理
场景,Word文档里内容是一个JSON串,读取后直接转为对象即可。
/** * 测试demo * * @param * @return */ @PostMapping("/word") public void mockPaper(@RequestParam("file") MultipartFile file) { try { String fileName = file.getOriginalFilename(); int lastIndexOf = fileName .lastIndexOf("."); if(lastIndexOf==-1) { throw new IllegalArgumentException("当前传入的文件格式不合法!"); } String suffex=fileName.substring(lastIndexOf+1); if("docx".equals(suffex)){ InputStream inputStream = file.getInputStream(); XWPFDocument xwpfDocument = new XWPFDocument(inputStream); XWPFWordExtractor extractor = new XWPFWordExtractor(xwpfDocument); //从Word文档中提取文本 String text = extractor.getText(); inputStream.close(); xwpfDocument.close(); extractor.close(); /* 分页获取 List
paragraphs = xwpfDocument.getParagraphs(); for(XWPFParagraph paragraph : paragraphs){ String words = paragraph.getText(); } Iterator iterator = xwpfDocument.getParagraphsIterator(); while (iterator.hasNext()) { XWPFParagraph para = iterator.next(); }*/ }else if("doc".equals(suffex)){ InputStream inputStream = file.getInputStream(); //使用HWPF组件中WordExtractor类从Word文档中提取文本或段落 WordExtractor wordExtractor = new WordExtractor(inputStream); String s = wordExtractor.getText(); inputStream.close(); wordExtractor.close(); }else { throw new IllegalArgumentException("不能解析的文档类型,请输入正确的word文档类型的文件!"); } } catch (IOException e) { e.printStackTrace(); } }
其二,使用POI操作Excel
老规矩,先上POM
org.apache.poi poi-ooxml-schemas 4.1.0 org.apache.poi poi-ooxml 4.1.0
场景:根据Excel文档数据(约一万条,九十多列),根据文档单条某一列的值,去查询数据库判断是否存在对应,若存在则将数据保存在临时文件中,不存在则跳过。
思路:文档数据转DTO集合,批量查询数据库并判断单条是否存在对应,批量写入临时文件。
相关代码已作脱敏处理,导入接口
/** * Excel导入 * * @param file 入参 * @return RespDto 出参 */ @ResponseBody @PostMapping("/importExcel") @ApiOperation("Excel导入") @ControllerLog(desc = "Excel导入") public RespDto
> importExcel(MultipartFile file) { RespDto > result = new RespDto<>(new SingleVo<>(Boolean.FALSE)); try { if(fundPoolService.importFund(file)){ result.setData(new SingleVo<>(Boolean.TRUE)); } } catch (Exception e) { log.error("importExcel 文件导入异常 error {}.param:{}",e,JSONObject.toJSONString(file)); result.setCode(ResultCodeEnum.UPDATE_ERR.getCode()).setMsg(e.getMessage()); } return result; } Excel导入,Service层代码接口
/** * Excel导入 * * @param file 入参 * @return boolean 出参 */ @Transactional(rollbackFor = Exception.class) public boolean importExcel(MultipartFile file) throws IOException{ AssertUtil.assertNotNull(file, "importExcel file is not allow null or empty"); List
xxList = new xxExcelUtil().readExcelFile(file.getInputStream(),file.getOriginalFilename()); if(ObjectUtils.isEmpty(xxList)){ throw new BusinessException("Excel导入异常,上传文件格式有误"); } //将成功数据写入临时文件 createResultFile(xxList); return true; } 构造通用的Excel处理工具
public class xxExcelUtil { public List
readExcelFile(InputStream inputStream, String fileName) throws IOException { Workbook workbook = null; try { //判断什么类型文件 if (fileName.endsWith(".xls")) { workbook = new HSSFWorkbook(inputStream); } else if (fileName.endsWith(".xlsx")) { workbook = new XSSFWorkbook(inputStream); }else { return null; } //获取所有的工作表的的数量,这里SheetNum数值应为1 int numOfSheet = workbook.getNumberOfSheets(); if(numOfSheet!=1){ return null; } //获取一个sheet也就是一个工作表 Sheet sheet = workbook.getSheetAt(numOfSheet-1); //获取一个sheet有多少Row int lastRowNum = sheet.getLastRowNum(); Row row; List xxList = new ArrayList<>(); for (int j = 1; j <= lastRowNum; j++) { row = sheet.getRow(j); if (row == null) { continue; } //获取一个Row有多少Cell short lastCellNum = row.getLastCellNum(); xx xx= new xx(); for (int k = 0; k <= lastCellNum; k++) { //根据自己的业务处理 } xxList.add(xx); } //返回结果集 return xxList; } catch (Exception e) { return null; }finally { inputStream.close(); if(!ObjectUtils.isEmpty(workbook)){ workbook.close(); } } } } 在这里可以看到,批量查询博主并没有采用常规的Mybatis in+拼接的方式,而采用了查取表中所有数据,通过一次遍历及唯一标识的方式,原因其一,九千条采用拼接的方式,会使查询SQL非常长,其二查询效率比如下这种方式低好几个档次。
特别提醒:但是这种基于内存的判断也有其本身的限制,因数据库表xx条数约两千多条,其日后不会有大变动,因此选择这种方式非常合适。但是如果表中数据条数非常多,或者不确定日后表中数据增长上升的速度,不建议采用这种方式。
/** * 将成功数据写入临时文件 * * @param * @return */ private void createResultFile(List
xxList){ //从数据库查取所有数据的唯一标识 List codeList = xxMapper.selectAllCode(); if(ObjectUtils.isEmpty(codeList)){ throw new BusinessException("将成功数据写入临时文件异常,从数据库查取所有产品集为空"); } StringBuilder prodBuilder = new StringBuilder(); for (xx xx: xxList){ if(codeList.contains(xx.getCode())){ prodBuilder.append(prodInfo.getCode()).append(",") .append(prodInfo.getName()).append(",") .append(prodInfo.getScore()).append("\r\n"); } } //写入文件 FileUtil.writeString(prodBuilder.toString(),fixTempFile(),"UTF-8"); } 定位临时文件这个方法,有一个特别注意的地方,因为WIN和LINUX路径分隔符并不一致,而 File.separator 可根据项目部署的系统选择对应的分隔符,好用的爆炸。
/** * 定位临时文件 * * @param * @return */ private String fixTempFile(){ //定位临时文件 String resultFilePath = new File(this.getClass().getResource("").getPath()).toString(); String filePath = resultFilePath.substring(0, resultFilePath.lastIndexOf(File.separator) ) + File.separator + "resultTxt" + File.separator ; return filePath + FundConstant.FILE_NAME; }
CSV文件导出先占个坑,下篇出
OK,使用POI处理常见的文件类型,圆满完结
✿✿✿ヽ(°▽°)ノ✿✿✿ヽ(°▽°)ノ✿✿✿ヽ(°▽°)ノ✿✿✿ヽ(°▽°)ノ✿✿✿