简介

  • poi就是提供api给java,以处理Microsoft office文件得工具包
  • 常用得POI有apache POI和阿里得easyPOI推荐使用阿里得easyPOI因为apache的io比较耗费内存,文件比较大时可能内存溢出(要看具体的工作簿类是哪个)

    excel结构

  • Microsoft office的excel分为2个版本,一个是03版本,一个是07版本!

    • 03的excel为**xls**,只能放65536行数据,即普通的excel
    • 07的excel为**xlsx**,行数无限制,即excel ooxml
  • 一个excel有工作簿(即整个文件)工作表(即一个excel里还有些子表sheet),然后就是行,列,单元格

    依赖

  • poi的不同版本的使用区别比较大,我们尽量使用曾经使用过的**4.1.0**或者是**3.9**,优先使用**4.1.0**

    1. <!-- 03-->
    2. <dependency>
    3. <groupId>org.apache.poi</groupId>
    4. <artifactId>poi</artifactId>
    5. <version>3.9/4.1.0</version>
    6. </dependency>
    7. <!-- 07-->
    8. <dependency>
    9. <groupId>org.apache.poi</groupId>
    10. <artifactId>poi-ooxml</artifactId>
    11. <version>3.9/4.1.0</version>
    12. </dependency>

    Apache POI

    分类

  • apache poi里一般如下分类,Workbook结构接口有不同种类的实现类,如工作簿接口有hssf工作簿实现类,xssf工作簿实现类

  • 不同类型的excel切换下工作簿实现类和最后输出的文件后缀即可,不过sxssf需要额外设置个自动删除临时文件

    • HSSF -提供读写MicrosoftExcel格式档案的功能。
      • hssf为03版excel,行数有限制。写过程写入缓存,不操作磁盘,最后一次写才写入磁盘,速度较快
    • XSSF -提供读写MicrosoftExcel OOXML格式档案的功能。
      • 为07版excel,写速度比较慢,耗费内存,不过行数无限制,但是行数太多时可能内存溢出
    • **SXSSF** XSSF的升级版,速度更快,耗费内存更少,但是会生成一个临时文件,需要手动删除
    • HWPF-提供读写MicrosoftWord格式档案的功能。
    • HSLF -提供读写Microsof PowerPoint格式档案的功能。
    • HDGF-提供读写MicrosoftVisio格式档案的功能

      Excel结构类

  • 工作簿:Workbook

  • 工作表:Sheet
  • 行:Row 行的行位置索引0开始
  • 单元格:Cell每一行的单元格位置索引0开始

    Excel写

  • 注意如果是循环生成一行的多个单元格,row对象一定要在循环外,在内部理论上是至少会有一个单元格生成,实际上一个都不会有

    1. String PATH="D:\\";
    2. Workbook workbook = new HSSFWorkbook();
    3. Sheet sheet = workbook.createSheet("我的工作薄");
    4. // 创建第一行
    5. Row row1 = sheet.createRow(0);
    6. // 创建第一行第一个单元格
    7. Cell cell11 = row1.createCell(0);
    8. cell11.setCellValue("日期");
    9. // 创建第一行第二个单元格
    10. Cell cell12 = row1.createCell(1);
    11. cell12.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
    12. // 创建第二行
    13. Row row2 = sheet.createRow(1);
    14. // 创建第二行第一个单元格
    15. Cell cell21 = row2.createCell(0);
    16. cell21.setCellValue("姓名");
    17. Cell cell22 = row2.createCell(1);
    18. cell22.setCellValue("Jago");
    19. // 写入excel
    20. FileOutputStream fos = new FileOutputStream(PATH + "poi.xls");
    21. workbook.write(fos);
    22. fos.close();
    23. }
    1. 不同类型的excel切换下工作簿实现类和最后输出的文件后缀即可,不过sxssf需要删除临时文件
    2. //在流关闭后加
    3. ((SXSSFWorkbook) workbook).dispose(); //自动删除临时文件

    Excel读

    1. Workbook workbook = new HSSFWorkbook(InputStream); //传个输入流
    2. Sheet sheet= workbook.getSheetAt(?);
    3. Row row=sheet.getRow(?);
    4. int x=sheet.getLastRowNum(); //返回任意一行的总列数(包括最后一列前的空列,实际使用时对于空列计算不太准确,如果一行都是非空列,建议使用下面那个方法计算)
    5. int x2=row.getPhysicalNumberOfRows(); 获取某一行非空列的列数
    6. int y=row.getLastCellNum(); //返回总行数-1(对于非第一行开始的空行不知道会不会计入)
    7. Cell cell=row.getCell(?) //获取某行的某个单元格
    8. cell.getStringCellValue(); //获取单元格值,还有很多其他类型方法,读取不同类型的值
    9. //.getNumbericCellValue()

    ```java Cell cell=…; int type=cell.getCellType(); HSSFCell.CELL_TYPE_STRING //这个枚举类里存了类型对应的int值,BLANk为空类型,ERROR为识别失败时的返回

    1. .CELL_TYPE_BLANK
    2. ...

    //对于时间,数字等值,枚举类型都为HSSFCell.CELL_TYPE_NUMERIC


- 读取日期或者数字,对于时间,数字等值,枚举类型都为`HSSFCell.CELL_TYPE_NUMERIC`  通过如下操作可以再进行类型识别   ,数字读取为字符串是为了防止数字过大

![image.png](https://cdn.nlark.com/yuque/0/2022/png/2319994/1648897757388-d5e70b88-3e7f-4c3d-9613-6ea7d621e463.png#clientId=ub310798f-bd44-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=145&id=u9a27976e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=270&originWidth=752&originalType=binary&ratio=1&rotation=0&showTitle=false&size=193437&status=done&style=none&taskId=ue28783f5-9d9e-4188-b126-19d4235662b&title=&width=402.6000061035156)
<a name="jQ4C8"></a>
## excel样式
```java
//单元格样式 4.1.0以上版本,不过如果这个4.1.0以上的用不了3.9的也尝试下
CellStyle comm = workbook.createCellStyle();
comm.setAlignment(HorizontalAlignment.CENTER); //水平居中
comm.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
comm.setBorderTop(BorderStyle.THIN);  //顶部边框设置,THIN就是黑色实线
cell.setCellStyle(comm);  //设置一个单元格的样式
//3.9以下版本单元格样式
XSSFCellStyle setBorder = (XSSFCellStyle) workbook.createCellStyle();
setBorder.setBorderBottom(XSSFCellStyle); //下边框

//列宽设置
sheet.setColumnWidth(index,15*256);  //index为第几列,0开始   单位为一个字符长度的256分之一。大概15个字符长度相当于7个中文字符的长度

EasyPOI

  • **easypoi**本质还是基于**apache poi**,更加快速高效,且解决了apache poi大数据量时容易**oom**的问题

    Excel写

  • 定义列,会同时生成标题行和内容行:@ExcelProperty

  • 忽略字段:@ExcelIgnore

    @Data
    public class DemoData {
      @ExcelProperty("字符串标题")   //标题默认为灰色,顺序为实体类中定义的顺序
      private String string;
      @ExcelProperty("数字标题")
      private Double doubleData;
      /**
       * 忽略这个字段
       */
      @ExcelIgnore
      private String ignore;
    }
    //write第一个参数可以传要生成的文件的路径与名字,也可以传个输出流
    //第二个参数传实体类class
    //doWrite传实体类的List
    //默认是输出07版本的格式
    String fileName = PATH + "EasyTest.xlsx";
    EasyExcel.write(fileName, DemoData.class).sheet("sheet1").doWrite(List<DemoData>);
           .write().excelType(ExcelTypeEnum.XLS)   //创建03版本
    

    Excel读

    EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    //传入文件路径或者输入流接收    DemoDataListener是一个定义的读取工具
    

    DemoDataListener.java

    hutool工具生成Excel

  • hutool工具的导出好处在于简单,支持的数据格式多(除了实体类List,还可以List<List> List<Map>) 注意使用hutool的ExcelWriter对象.flush(输出流);生成excel时,如果该流是HttpServletResponse,那么flush写完后会自动将流关闭。此时如果控制器返回数据就会报错,因为流已经关闭不能二次调用。所以控制器方法应该返回null

    模板引擎-Word

    word结构

  • word本身就是一种特殊的xml文件,我们可以先定义好word的模板然后另存为word-xml格式,再将xml后缀改为ftl。(这里使用freemarek模板引擎)

POI - 图1

模板

  • 比如一个Word表格

    <#list infolist as listKey>
      <w:tr>
          <w:t>${listKey.name}</w:t>
      </w:tr>
    </#list>
    

    工具类

  • 传入一个map,map里可以存list或者是非集合数据,模板ftl存在resource下。

    • map里的对象不要有null,map里的list的元素也是。否则很容易报错。另外模板里渲染的值在map里必须存在,否则也会报错
  • 设置了include文件打包过滤的,记得把ftl文件也设置打包进去<include>**/*.ftl</include>

    public static void createWordrzd(Map<?, ?> dataMap, String templateName,
                                    String filePath, String fileName) {
          try {
              Configuration configuration = new Configuration();
    
              configuration.setDefaultEncoding("UTF-8");
              // ftl模板文件的resource的位置
              configuration.setClassForTemplateLoading(WordUtils.class, "/templates/");
              // 获取模板
              Template template = configuration.getTemplate(templateName);
              File outFile = new File(filePath + File.separator + fileName);
              if (!outFile.getParentFile().exists()) {
                  outFile.getParentFile().mkdirs();
              }
              // 将模板和数据模型合并生成文件
              Writer out = new BufferedWriter(new OutputStreamWriter(
                      new FileOutputStream(outFile), "UTF-8"));
              // 生成文件
              template.process(dataMap, out);
              out.flush();
              out.close();
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    

    调用

    @GetMapping("/daying")
      public void daying(HttpServletResponse response){
      Map<String, Object> dataMap = new HashMap<>();
          dataMaP.put("title","xxx");
          dataMap.put("infolist",new ArrayList<List>());
          //生成docx到本地
          WordUtils.createWordrzd(dataMap,"huizong.ftl","D:/backup/","sdf.docx");
          //再获取本地生成的docx文件流,然后删除生成的docx
          File file=new File("D:/backup/sdf.docx");
          FileInputStream fin = new FileInputStream(file);
          response.setCharacterEncoding("UTF-8");
          response.setContentType("application/msword");
          // 设置浏览器以下载的方式处理该文件名
    
          response.setHeader("Content-Disposition", "attachment;filename="
                  .concat(String.valueOf(URLEncoder.encode("huizong", "UTF-8"))));
    
          OutputStream out = response.getOutputStream();
          byte[] buffer = new byte[1024]; // 缓冲区
          int bytesToRead = -1;
          // 通过循环将读入的Word文件的内容输出到浏览器中
          while((bytesToRead = fin.read(buffer)) != -1) {
              out.write(buffer, 0, bytesToRead);
          }
          if(fin != null) fin.close();
          if(out != null) out.close();
          if(file != null) file.delete();
      }
    

    前端打印

    function PrintForm() {
          $("#daying").hide();
          var strHead = "<head>" + document.getElementById("head1").innerHTML + "</head>";
      //这个代码亏内
          var strFormHtml = strHead + "<body style=\"font-size:16px\">" + $("#div_form").html() + "</body>";
          var isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows")
          if (isWin) {
              var userAgent = navigator.userAgent;
              if (userAgent.indexOf("Chrome") > -1) {
                  var strPrintStyle = "<style media='print'>@page {size: auto;margin: 0mm 15mm 0mm 15mm;}</style><div style='height: 25px;'></div>"; //去掉页眉页尾
                  window.document.body.innerHTML = strPrintStyle + strFormHtml;
                  window.print();
                  window.location.href = window.location.href;
              } else {
                  var LODOP = getLodop(document.getElementById('LODOP_OB'), document.getElementById('LODOP_EM'));
                  LODOP.PRINT_INIT("打印_表单");
                  LODOP.SET_PRINT_PAGESIZE(1, 0, 0, "A4");
                  LODOP.ADD_PRINT_HTM("0%", "4%", "80%", "100%", strFormHtml);
                  LODOP.PREVIEW();
              }
          } else {
              var strPrintStyle = "<style media='print'>@page {size: auto;margin: 0mm 15mm 0mm 15mm;}</style><div style='height: 25px;'></div>"; //去掉页眉页尾
              window.document.body.innerHTML = strPrintStyle + strFormHtml;
              window.print();
              window.location.href = window.location.href;
          }
          $("#daying").show();
      }