报表技术笔记

1.Excel说明

  • 在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式:
    • 为了方便操作,基于Excel的报表批量上传数据,也就是把Excel中的数据导入到系统中。
    • 通过java代码生成Excel报表。也就是把系统中的数据导出到Excel中,方便查阅。

1.Excel的两种版本

  • 目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本,两者之间的区别如下:

    image-20220610220507237

    • Excel2003 是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小
    • Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高
  • Java中常见的用来操作Excel的方式一般有2种:JXLPOI

1.1 JXL

JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护

  • 使用jxl导出excel

         <dependency>
                <groupId>net.sourceforge.jexcelapi</groupId>
                <artifactId>jxl</artifactId>
                <version>2.6.12</version>
            </dependency>
    

    1、 创建可写入的Excel工作薄

    WritableWorkbook workbook= Workbook.createWorkbook(输出流);
    

    2、创建工作表

    WritableSheet sheet= workbook.createSheet(工作表的名称, 工作表的索引值);
    

    3、创建单元格

    添加文本类单元格

    Label labelC = new Label(列索引值, 行索引值, "单元格中的内容");
    
    sheet.addCell(labelC);
    

    4、写入到文件

    workbook.write();// 写入数据
    

    5、释放资源:

    workbook.close();// 关闭文件
    
 public void downLoadByJxl(HttpServletResponse response) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        try {
            ServletOutputStream sos = response.getOutputStream();
            // 1.创建一个工作薄
            WritableWorkbook workbook = Workbook.createWorkbook(sos);
            // 2.创建工作表
            WritableSheet sheet = workbook.createSheet("创建入门JXL", 0);
            // 设置列宽
            sheet.setColumnView(0,5);
            sheet.setColumnView(1,8);
            sheet.setColumnView(2,15);
            sheet.setColumnView(3,15);
            sheet.setColumnView(4,30);
            // 3.标题
            String[] titles = new String[]{"编号", "姓名", "手机号", "入职日期", "现住址"};
            Label label = null;
            for (int i = 0; i < titles.length; i++) {
                label = new Label(i, 0, titles[i]);
                sheet.addCell(label);
            }
            // 4.处理导出的内容
            List<User> userList = findAll();
            int rowCount = 1;
            for (User user : userList) {
                label = new Label(0, rowCount, user.getId().toString());
                sheet.addCell(label);

                label = new Label(1, rowCount, user.getUserName());
                sheet.addCell(label);

                label = new Label(2, rowCount, user.getPhone());
                sheet.addCell(label);

                label = new Label(3, rowCount, formatter.format(user.getHireDate()));
                sheet.addCell(label);

                label = new Label(4, rowCount, user.getAddress());
                sheet.addCell(label);
                rowCount++;
            }
            // 5.导出的文件名称
            String filename = "一个JXL入门.xls";
            // 6.设置文件的打开方式和mime类型
            response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
            response.setContentType("application/vnd.ms-excel");
            // 7.导出
            workbook.write();
            // 8.关闭资源
            workbook.close();
            sos.close();
        } catch (IOException | WriteException e) {
            e.printStackTrace();
        }
    }

image-20220610234308950

1.2 POI

POI是apache的项目,可对微软的Word,Excel,PPT进行操作,包括office2003和2007,Excle2003和2007,poi现在一直有更新。所以现在主流使用POI。

Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache
POI提供API给Java语言操作Microsoft Office的功能

image-20220610231526962

  • 在POI包中有如下几个主要对象和excel的几个对象对应:
对应excel名称低版本中的类名高版本中的类名
工作簿HSSFWorkbookXSSFWorkbook
工作表HSSFSheetXSSFSheet
HSSFRowXSSFRow
单元格HSSFCellXSSFCell
单元格样式HSSFCellStyleXSSFCellStyle
  • 入门案例创建Excel

           <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>4.0.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>4.0.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml-schemas</artifactId>
                <version>4.0.1</version>
            </dependency>
    
    public class PoiDemo1 {
        public static void main(String[] args) throws IOException {
            // 1.创建一个工作薄
            XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
            // 2.创建一个工作表
            XSSFSheet xssfSheet = xssfWorkbook.createSheet("PoiDemo");
            // 3.在工作表中创建行
            XSSFRow xssfRow = xssfSheet.createRow(0);
            // 4.在行中创建单元格
            XSSFCell xssfCell = xssfRow.createCell(0);
            // 5.在单元格中写入内容
            xssfCell.setCellValue("pio写入内容");
    
           xssfWorkbook.write(new FileOutputStream("/Users/liubo/typora/a.xlsx"));
        }
    }
    
  • 导入Excel数据添加到DB

  public void uploadExcel(MultipartFile file) {

        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        try (InputStream inputStream = file.getInputStream()) {
            Workbook workbook = new XSSFWorkbook(inputStream);
            Sheet sheet = workbook.getSheetAt(0);
            // 1.获取到最后一行的索引
            int lastRowIndex = sheet.getLastRowNum();
            // 2.遍历每一行 从每一行里面拿到单元格的数据
            Row row;
            User user = null;
            for (int i = 1; i <= lastRowIndex; i++) {
                row = sheet.getRow(i);
                // 用户名 手机号 省份 城市 工资 入职日期 出生日期 现住地址
                String userName = row.getCell(0).getStringCellValue();
                // 处理数据格式问题
                String phone = null;
                try {
                    phone = row.getCell(1).getStringCellValue();
                } catch (Exception e) {
                    phone = row.getCell(1).getNumericCellValue() + "";
                }
                String provinces = row.getCell(2).getStringCellValue();
                String city = row.getCell(3).getStringCellValue();
                int salary = ((Double) row.getCell(4).getNumericCellValue()).intValue();
                Date hireDate = df.parse(row.getCell(5).getStringCellValue());
                Date birthDate = df.parse(row.getCell(6).getStringCellValue());
                String address = row.getCell(7).getStringCellValue();
                // 3.创建user对象 插入到db
                user = User.builder().userName(userName).phone(phone).province(provinces)
                        .city(city).salary(salary).hireDate(hireDate).birthday(birthDate)
                        .birthday(birthDate).address(address).build();
                userMapper.insertSelective(user);
            }
        } catch (Exception e) {
        }
1.2.1 POI设置样式
    public void downLoadXlsxByPoi(HttpServletResponse response) throws IOException {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("有样式的数据");

        sheet.setColumnWidth(0, 5 * 256);
        sheet.setColumnWidth(1, 8 * 256);
        sheet.setColumnWidth(2, 10 * 256);
        sheet.setColumnWidth(3, 10 * 256);
        sheet.setColumnWidth(4, 30 * 256);
        // 设置标题样式 1.边框线全边框 2.行高 3.合并单元格 4.水平居中 5.黑体18号字
        CellStyle bigTitleRowCellStyle = workbook.createCellStyle();
        bigTitleRowCellStyle.setBorderTop(BorderStyle.THIN);
        bigTitleRowCellStyle.setBorderBottom(BorderStyle.THIN);
        bigTitleRowCellStyle.setBorderLeft(BorderStyle.THIN);
        bigTitleRowCellStyle.setBorderRight(BorderStyle.THIN);
        // 水平对齐 垂直对齐
        bigTitleRowCellStyle.setAlignment(HorizontalAlignment.CENTER);
        bigTitleRowCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置字体
        Font font = workbook.createFont();
        font.setFontName("黑体");
        font.setFontHeightInPoints((short) 18);
        bigTitleRowCellStyle.setFont(font);

        Row bigTitleRow = sheet.createRow(0);
        bigTitleRow.setHeightInPoints(42); // 设置行高
        for (int i = 0; i < 5; i++) {
            // 创建五个单元格并设置样式
            Cell cell = bigTitleRow.createCell(i);
            cell.setCellStyle(bigTitleRowCellStyle);
        }
        // 合并单元格
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 4));
        sheet.getRow(0).getCell(0).setCellValue("用户信息展示");

        // 设置小标题样式 1.边框线全边框 2.行高 3.合并单元格 4.水平居中 5.黑体18号字
        CellStyle littleTitleRowCellStyle = workbook.createCellStyle();
        littleTitleRowCellStyle.cloneStyleFrom(bigTitleRowCellStyle);
        // 设置字体
        Font littleTitleRowFont = workbook.createFont();
        littleTitleRowFont.setFontName("宋体");
        littleTitleRowFont.setFontHeightInPoints((short) 12);
        // 设置黑体
        littleTitleRowFont.setBold(true);
        littleTitleRowCellStyle.setFont(littleTitleRowFont);

        // 设置小标题样式 1.边框线全边框 2.行高 3.合并单元格 4.水平居中 5.黑体18号字
        CellStyle valueTitleRowCellStyle = workbook.createCellStyle();
        valueTitleRowCellStyle.cloneStyleFrom(bigTitleRowCellStyle);
        valueTitleRowCellStyle.setAlignment(HorizontalAlignment.LEFT);
        // 设置字体
        Font valueTitleRowFont = workbook.createFont();
        valueTitleRowFont.setFontName("宋体");
        valueTitleRowFont.setFontHeightInPoints((short) 11);
        // 设置黑体
        valueTitleRowFont.setBold(false);
        valueTitleRowCellStyle.setFont(valueTitleRowFont);

        Row littleTitleRow = sheet.createRow(1);
        littleTitleRow.setHeightInPoints(31.5F);
        String[] titles = new String[]{"用户", "姓名", "手机号", "入职日期", "现住址"};
        for (int i = 0; i < titles.length; i++) {
            Cell cell = littleTitleRow.createCell(i);
            cell.setCellValue(titles[i]);
            cell.setCellStyle(littleTitleRowCellStyle);
        }

        List<User> users = userMapper.selectAll();

        int rowIndex = 2;
        Row row = null;
        Cell cell = null;
        for (User user : users) {
            row = sheet.createRow(rowIndex);
            cell = row.createCell(0);
            cell.setCellStyle(valueTitleRowCellStyle);
            cell.setCellValue(user.getId());

            cell = row.createCell(1);
            cell.setCellStyle(valueTitleRowCellStyle);
            cell.setCellValue(user.getUserName());

            cell = row.createCell(2);
            cell.setCellStyle(valueTitleRowCellStyle);
            cell.setCellValue(user.getPhone());

            cell = row.createCell(3);
            cell.setCellStyle(valueTitleRowCellStyle);
            cell.setCellValue(formatter.format(user.getHireDate()));

            cell = row.createCell(4);
            cell.setCellStyle(valueTitleRowCellStyle);
            cell.setCellValue(user.getAddress());

            rowIndex++;
        }

        String fileName = "员工数据.xlsx";
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(), "ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

        workbook.write(response.getOutputStream());
    }

image-20220611134812384

1.2.2 使用模版导出图片
  • 个人信息的导出中包含了头像照片,需要用到POI的导出图片功能,那么POI主要提供了两个类来处理照片,这两个类是Patriarch和ClientAnchor前者负责在表中创建图片,后者负责设置图片的大小位置。

  • 关于XSSFClientAnchor的8个参数说明:

    dx1 - the x coordinate within the first cell.//定义了图片在第一个cell内的偏移x坐标,既左上角所在cell的偏移x坐标,一般可设0
    dy1 - the y coordinate within the first cell.//定义了图片在第一个cell的偏移y坐标,既左上角所在cell的偏移y坐标,一般可设0
    dx2 - the x coordinate within the second cell.//定义了图片在第二个cell的偏移x坐标,既右下角所在cell的偏移x坐标,一般可设0
    dy2 - the y coordinate within the second cell.//定义了图片在第二个cell的偏移y坐标,既右下角所在cell的偏移y坐标,一般可设0
    
    col1 - the column (0 based) of the first cell.//第一个cell所在列,既图片左上角所在列
    row1 - the row (0 based) of the first cell.//图片左上角所在行
    col2 - the column (0 based) of the second cell.//图片右下角所在列
    row2 - the row (0 based) of the second cell.//图片右下角所在行
    
    public void download(Long id, HttpServletResponse response) throws IOException, InvalidFormatException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        // 获取模板的路径
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
        File templatePath = new File(rootPath.getAbsolutePath(), "/excel_template/userInfo.xlsx");
        //读取模板文件产生workbook对象,这个workbook是一个有内容的工作薄
        Workbook workbook = new XSSFWorkbook(templatePath);
        //读取工作薄的第一个工作表,向工作表中放数据
        Sheet sheet = workbook.getSheetAt(0);
        //处理内容
        User user = userMapper.selectByPrimaryKey(id);
        //接下来向模板中单元格中放数据
        //用户名   第2行第2列
        sheet.getRow(1).getCell(1).setCellValue(user.getUserName());
        //手机号   第3行第2列
        sheet.getRow(2).getCell(1).setCellValue(user.getPhone());
        //生日     第4行第2列  日期转成字符串
        sheet.getRow(3).getCell(1).setCellValue
                (simpleDateFormat.format(user.getBirthday()));
        //工资 第5行第2列
        sheet.getRow(4).getCell(1).setCellValue(user.getSalary());
        //工资 第6行第2列
        sheet.getRow(5).getCell(1).setCellValue
                (simpleDateFormat.format(user.getHireDate()));
        //省份     第7行第2列
        sheet.getRow(6).getCell(1).setCellValue(user.getProvince());
        // 现住址   第8行第2列
        sheet.getRow(7).getCell(1).setCellValue(user.getAddress());
        //司龄     第6行第4列暂时先不考虑

        //城市     第7行第4列
        sheet.getRow(6).getCell(3).setCellValue(user.getCity());
        // 先创建一个字节输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        File file = new File(rootPath + user.getPhoto());
        // BufferedImage是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中
        BufferedImage bufferImg = ImageIO.read(file);
        // 把读取到图像放入到输出流中
        ImageIO.write(bufferImg, "jpg", bos);
        // 创建一个绘图控制类,负责画图
        Drawing drawingPatriarch = sheet.createDrawingPatriarch();
        // 指定把图片放到哪个位置
        ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, 2, 1, 4, 5);
        // 开始把图片写入到sheet指定的位置
        drawingPatriarch.createPicture(anchor, workbook.addPicture(bos.toByteArray(), Workbook.PICTURE_TYPE_JPEG));
        //导出的文件名称
        String filename = "用户详细信息数据.xlsx";
        //设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);

    }

image-20220611202833226

司龄计算公式: =CONCATENATE(DATEDIF(B6,TODAY(),“Y”),“年”,DATEDIF(B6,TODAY(),“YM”),“个月”)

1.2.3自定义引擎
  • 看我们刚才导出时写的代码,必须要提前知道要导出数据在哪一行哪一个单元格,但是如果模板一旦发生调整,那么我们的java代码必须要修改,我们可以自定义个导出的引擎,有了这个引擎即使模板修改了我们的java代码也不用修改
 public static Workbook writeToExcel(Object o, Workbook workbook, String imagePath) throws IOException {
        Map<String, Object> map = EntityUtils.entityToMap(o);
        Sheet sheet = workbook.getSheetAt(0);
        // 循环遍历100行 ,每一行遍历100个单元格
        Row row = null;
        Cell cell = null;
        for (int i = 0; i < 100; i++) {
            row = sheet.getRow(i);
            if (row == null) {
                break;
            } else {
                for (int j = 0; j < 100; j++) {
                    cell = row.getCell(j);
                    if (cell != null) {
                        writeToCell(cell, map);
                    }
                }
            }
        }
        // 先创建一个字节输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        File file = new File(imagePath);
        // BufferedImage是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中
        BufferedImage bufferImg = ImageIO.read(file);
        // 把读取到图像放入到输出流中
        // 图片类型
        String extName = imagePath.substring(imagePath.lastIndexOf(".") + 1).toUpperCase();
        ImageIO.write(bufferImg, extName, bos);
        // 创建一个绘图控制类,负责画图
        Drawing drawingPatriarch = sheet.createDrawingPatriarch();
        // 指定把图片放到哪个位置
        Sheet sheetAt = workbook.getSheetAt(1);
        // 使用第二个sheet 来决定图片的位置
        int col1 = ((Double) sheetAt.getRow(0).getCell(0).getNumericCellValue()).intValue();
        int row1 = ((Double) sheetAt.getRow(0).getCell(1).getNumericCellValue()).intValue();
        int col2 = ((Double) sheetAt.getRow(0).getCell(2).getNumericCellValue()).intValue();
        int row2 = ((Double) sheetAt.getRow(0).getCell(3).getNumericCellValue()).intValue();

        ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, col1, row1, col2, row2);
        // 开始把图片写入到sheet指定的位置
        int format = 0;

        switch (extName) {
            case "JPG": {
                format = XSSFWorkbook.PICTURE_TYPE_JPEG;
            }
            case "JPEG": {
                format = XSSFWorkbook.PICTURE_TYPE_JPEG;
            }
            case "PNG": {
                format = XSSFWorkbook.PICTURE_TYPE_PNG;
            }
        }
        drawingPatriarch.createPicture(anchor, workbook.addPicture(bos.toByteArray(), format));
        // 删除第二个sheet
        workbook.removeSheetAt(1);

        return workbook;
    }

    private static void writeToCell(Cell cell, Map<String, Object> map) {

        CellType cellType = cell.getCellType();
        switch (cellType) {
            case FORMULA: {
                break;
            }
            default: {
                String cellValue = cell.getStringCellValue();
                if (!StringUtils.isNoneBlank(cellValue)) {
                    break;
                }
                for (String key : map.keySet()) {
                    if (cellValue.equals(key)) {
                        cell.setCellValue(map.get(key).toString());
                    }
                }
            }
        }
    }
1.2.4 POI导入word
  • Api介绍

    • poi对低版本的doc本身支持的就不好所以我们直接说高版本的docx版本的api。

      1、poi操作word正文

      XWPFDocument代表一个docx文档,其可以用来读docx文档,也可以用来写docx文档

      一个文档包含多个段落,一个段落包含多个Runs文本,一个Runs包含多个Run,Run是文档的最小单元

      获取所有段落:List paragraphs = word.getParagraphs();

      获取一个段落中的所有片段Runs:List xwpfRuns = xwpfParagraph.getRuns();

      获取一个Runs中的一个Run:XWPFRun run = xwpfRuns.get(index);

      2、poi操作word中的表格

      一个文档包含多个表格,一个表格包含多行,一行包含多列单元格

      获取所有表格:List xwpfTables = doc.getTables();

      获取一个表格中的所有行:List xwpfTableRows = xwpfTable.getRows();

      获取一行中的所有列:List xwpfTableCells = xwpfTableRow.getTableCells();

      获取一格里的内容:List paragraphs = xwpfTableCell.getParagraphs();

      之后和正文段落一样

      public class PIoWordDemo {
          public static void main(String[] args) throws IOException {
              XWPFDocument document = new XWPFDocument(new FileInputStream("/Users/liubo/netdisk/第三天/资料/test.docx"));
              // 读取正文
              List<XWPFParagraph> paragraphs = document.getParagraphs();
              // 获取所有段落
              for (XWPFParagraph paragraph : paragraphs) {
                  // 获取所有片段
                  List<XWPFRun> runs = paragraph.getRuns();
                  for (XWPFRun run : runs) {
                      // 获取片段中的内容
                      System.out.println(run.getText(0));
                  }
              }
      
              // 获取所有表格
              List<XWPFTable> tables = document.getTables();
              for (XWPFTable table : tables) {
                  // 获取表格的所有行
                  List<XWPFTableRow> rows = table.getRows();
                  for (XWPFTableRow row : rows) {
                      // 获取每一行的所有列
                      List<XWPFTableCell> tableCells = row.getTableCells();
                      for (XWPFTableCell tableCell : tableCells) {
                          // 获取所有段落
                          List<XWPFParagraph> paragraphs1 = tableCell.getParagraphs();
                          // 获取列的内容
                          for (XWPFParagraph xwpfParagraph : paragraphs1) {
                              System.out.println(xwpfParagraph.getText());
                          }
                      }
                  }
              }
          }
      }
      
  • 实现导出word文档并附带图片

        public void downloadContract(Long id, HttpServletResponse response) throws IOException, InvalidFormatException {
            // 获取模板的路径
            File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
            File templatePath = new File(rootPath.getAbsolutePath(), "/word_template/contract_template.docx");
            XWPFDocument document = new XWPFDocument(new FileInputStream(templatePath));
            User user = findById(id);
            Map<String, String> params = new HashMap<>();
            params.put("userName", user.getUserName());
            params.put("hireDate", simpleDateFormat.format(user.getHireDate()));
            params.put("address", user.getAddress());
            // 处理正文开始
            List<XWPFParagraph> paragraphs = document.getParagraphs();
            for (XWPFParagraph paragraph : paragraphs) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    String text = run.getText(0);
                    for (String key : params.keySet()) {
                        if (key.equals(text)) {
                            run.setText(text.replaceAll(text, params.get(key)), 0);
                        }
                    }
                }
            }
            // 处理正文结束
    
            // 处理表格开始
            List<Resource> resourceList = user.getResourceList();
            XWPFTable xwpfTable = document.getTables().get(0);
            XWPFTableRow row = xwpfTable.getRow(0);
            int rowIndex = 1;
            for (Resource resource : resourceList) {
                // 添加行
                copyRow(xwpfTable, row, rowIndex);
                XWPFTableRow row1 = xwpfTable.getRow(rowIndex);
                row1.getCell(0).setText(resource.getName());
                row1.getCell(1).setText(resource.getPrice().toString());
                row1.getCell(2).setText(resource.getNeedReturn() ? "需求" : "不需要");
    
                File imageFile = new File(rootPath, "/static" + resource.getPhoto());
                setCellImage(row1.getCell(3), imageFile);
                rowIndex++;
            }
            // 处理表格结束
    
            //导出的文件名称
            String filename = "员工(" + user.getUserName() + ")合同.docx";
            //设置文件的打开方式和mime类型
            ServletOutputStream outputStream = response.getOutputStream();
            response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            document.write(outputStream);
        }
    
        // 设置图片
        private void setCellImage(XWPFTableCell cell, File imageFile) {
            XWPFRun run = cell.getParagraphs().get(0).createRun();
            try (FileInputStream inputStream = new FileInputStream(imageFile)) {
                run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_JPEG, imageFile.getName(), Units.toEMU(100), Units.toEMU(60));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 深克隆
        private void copyRow(XWPFTable xwpfTable, XWPFTableRow row, int rowIndex) {
            XWPFTableRow tableRow = xwpfTable.insertNewTableRow(rowIndex);
            // 设置行属性
            tableRow.getCtRow().setTrPr(row.getCtRow().getTrPr());
            // 获取原来的行的单元格
            List<XWPFTableCell> tableCells = row.getTableCells();
            if (CollectionUtils.isEmpty(tableCells)) {
                return;
            }
    
            for (XWPFTableCell tableCell : tableCells) {
                XWPFTableCell tableCell1 = tableRow.addNewTableCell();
                //            附上单元格的样式
                //            单元格的属性
                tableCell1.getCTTc().setTcPr(tableCell.getCTTc().getTcPr());
                tableCell1.getParagraphs().get(0).getCTP().setPPr(tableCell.getParagraphs().get(0).getCTP().getPPr());
            }
     }
    

2.百万数据导出

2.1概述

  • 对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:

    • ​ java代码解析xml
    • ​ dom4j:一次性加载xml文件再解析
    • ​ SAX:逐行加载,逐行解析
  • **用户模式:**用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)

  • **事件模式:**基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。

  • SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel

    • 在实例化SXSSFWorkBook这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。

    • 导出时使用的是SXSSFWorkBook这个类,一个工作表sheet最多只能放1048576行数据, 当我们的业务数据已超过100万了,一个sheet就不够用了,必须拆分到多个工作表。

      导出百万数据时有两个弊端:

      1、不能使用模板

      2、不能使用太多的样式

2.2实现

CREATE TABLE `tb_user2` (
  `id` bigint(20) NOT NULL  COMMENT '用户ID',
  `user_name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(15) DEFAULT NULL COMMENT '手机号',
  `province` varchar(50) DEFAULT NULL COMMENT '省份',
  `city` varchar(50) DEFAULT NULL COMMENT '城市',
  `salary` int(10) DEFAULT NULL,
  `hire_date` datetime DEFAULT NULL COMMENT '入职日期',
  `dept_id` bigint(20) DEFAULT NULL COMMENT '部门编号',
  `birthday` datetime DEFAULT NULL COMMENT '出生日期',
  `photo` varchar(200) DEFAULT NULL COMMENT '照片路径',
  `address` varchar(300) DEFAULT NULL COMMENT '现在住址' 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DELIMITER $$    -- 重新定义“;”分号
DROP PROCEDURE IF EXISTS test_insert $$   -- 如果有test_insert这个存储过程就删除
CREATE PROCEDURE test_insert()			  -- 创建存储过程

BEGIN
	DECLARE n int DEFAULT 1;				    -- 定义变量n=1
	SET AUTOCOMMIT=0;						    -- 取消自动提交
	
		while n <= 5000000 do					
			INSERT INTO `tb_user2` VALUES ( n, CONCAT('测试', n), '13800000001', '北京市', '北京市', '11000', '2001-03-01 21:18:29', '1', '1981-03-02 00:00:00', '\\static\\user_photos\\1.jpg', '北京市西城区宣武大街1号院');
			SET n=n+1;
		END while;
		COMMIT;
END $$
 public void downLoadMillion(HttpServletResponse response) throws IOException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        // 使用sax方式解析
        Workbook workbook = new SXSSFWorkbook();
        int page = 1;
        int rowIndex = 1;
        int num = 0;  //记录处理数据的个数
        Row row = null;
        Sheet sheet = null;
        while (true) {
            List<User> userList = this.findPage(page, 1000000);
            if (CollectionUtils.isEmpty(userList)) {
                break;
            }
            // 创建工作表
            if (num % 1000000 == 0) {
                sheet = workbook.createSheet("第" + (num / 100000 + 1) + "个工作表");
                rowIndex = 1;
                // 设置标题
                String[] title = new String[]{"编号", "姓名", "手机号", "入职日期", "现住址"};
                row = sheet.createRow(0);
                for (int i = 0; i < title.length; i++) {
                    row.createCell(i).setCellValue(title[i]);
                }
            }


            for (User user : userList) {
                row = sheet.createRow(rowIndex);
                row.createCell(0).setCellValue(user.getId());
                row.createCell(1).setCellValue(user.getUserName());
                row.createCell(2).setCellValue(user.getPhone());
                row.createCell(3).setCellValue(simpleDateFormat.format(user.getHireDate()));
                row.createCell(4).setCellValue(user.getAddress());
                rowIndex++;
                num++;
            }

            page++;
        }

        //导出的文件名称
        String filename = "百万数据导出.xlsx";
        //设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);
    }

3.百万数据的导入

3.1概述

  • 使用POI基于事件模式解析案例提供的Excel文件

  • 思路分析

    • **用户模式:**加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
    • **事件模式:**它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。
  • 步骤分析

    • 设置POI的事件模式,

      根据Excel获取文件流
      根据文件流创建OPCPackage 用来组合读取到的xml 组合出来的数据占用的空间更小
      创建XSSFReader对象

    • Sax解析
      自定义Sheet处理器
      创建Sax的XmlReader对象
      设置Sheet的事件处理器
      逐行读取

3.2实现

  • 自定义处理器

    public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {
        User user = null;
    
        @Override
        public void startRow(int rowIndex) {  //开始行
            if (rowIndex == 0) {   // 第一行标题不做处理
                user = null;
            } else {
                user = new User();
            }
        }
    
    
        @Override
        public void cell(String cellName, String cellValue, XSSFComment xssfComment) {  //处理每一行单元格
            if (user != null) {
                String letter = cellName.substring(0, 1);  //每个单元格的首字母
                switch (letter) {
                    case "A": {
                        user.setId(Long.parseLong(cellValue));
                        break;
                    }
                    case "B": {
                        user.setUserName(cellValue);
                        break;
                    }
                }
            }
        }
    
    
        @Override
        public void endRow(int rowIndex) { //结束行
            if (rowIndex != 0) {
                System.out.println(user);
            }
        }
    }
    
  • 自定义解析器

    public class ExcelParser {
    
        public void parse (String path) throws Exception {
            //1.根据Excel获取OPCPackage对象
            OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
            try {
                //2.创建XSSFReader对象
                XSSFReader reader = new XSSFReader(pkg);
                //3.获取SharedStringsTable对象
                SharedStringsTable sst = reader.getSharedStringsTable();
                //4.获取StylesTable对象
                StylesTable styles = reader.getStylesTable();
                XMLReader parser = XMLReaderFactory.createXMLReader();
                // 处理公共属性:Sheet名,Sheet合并单元格
                parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false));
                XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData();
                while (sheets.hasNext()) {
                    InputStream sheetstream = sheets.next();
                    InputSource sheetSource = new InputSource(sheetstream);
                    try {
                        parser.parse(sheetSource);
                    } finally {
                        sheetstream.close();
                    }
                }
            } finally {
                pkg.close();
            }
        }
    }
    

2.opencsv操作CSV文件

2.1 csv简介

CSV文件:Comma-Separated Values,中文叫逗号分隔值或者字符分割值,其文件以纯文本的形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分割。每条记录由字段组成,字段间的分隔符是其他字符或者字符串。所有的记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。
用文本文件、excel或者类似与文本文件的编辑器都可以打开CSV文件。

2.2常用api

  • Write

image-20220612143109890

  • Write方法

image-20220612143158256

  • Reader

image-20220612143359824

构造器涉及到的三个参数:

  1. reader:读取文件的流对象,常有的是BufferedReader,InputStreamReader。
  2. separator:用于定义前面提到的分割符,默认为逗号CSVWriter.DEFAULT_SEPARATOR用于分割各列。
  3. quotechar:用于定义各个列的引号,有时候csv文件中会用引号或者其它符号将一个列引起来,例如一行可能是:“1”,“2”,“3”,如果想读出的字符不包含引号,就可以把参数设为:"CSVWriter.NO_QUOTE_CHARACTER "
  • reader方法

    image-20220612143446452

2.3使用csv导出百万数据

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>4.5</version>
</dependency>
    public void downLoadCSV(HttpServletResponse response) throws IOException {

        //            准备输出流
        ServletOutputStream outputStream = response.getOutputStream();
        //            文件名
        String filename = "百万数据.csv";
        //            设置两个头 一个是文件的打开方式 一个是mime类型
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
        response.setContentType("text/csv");

        CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(outputStream, "utf-8"));
        // 先写标题

        csvWriter.writeNext(new String[]{"编号", "姓名", "手机号", "入职日期", "现住址"});

        // 查数据
        int page = 1;
        int pageSize = 200000;
        while (true) {
            List<User> userList = this.findPage(page, pageSize);
            if (CollectionUtils.isEmpty(userList)) {
                break;
            }

            for (User user : userList) {
                csvWriter.writeNext(new String[]{user.getId().toString(), user.getUserName(), user.getPhone(), simpleDateFormat.format(user.getHireDate()), user.getAddress()});
            }
            csvWriter.flush();
            page++;
        }
        csvWriter.close();
    }

2.4使用csv导出百万数据

//读取百万级数据的csv文件
public class CsvDemo {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) throws Exception {
        CSVReader csvReader = new CSVReader(new FileReader("d:\\百万用户数据的导出.csv"));
        String[] titles = csvReader.readNext(); //读取到第一行 是小标题
//        "编号","姓名","手机号","入职日期","现住址"
        User user = null;
        while (true){
            user = new User();
            String[] content = csvReader.readNext();
            if(content==null){
                break;
            }
            user.setId(Long.parseLong(content[0]));
            user.setUserName(content[1]);
            user.setPhone(content[2]);
            user.setHireDate(simpleDateFormat.parse(content[3]));
            user.setAddress(content[4]);
            System.out.println(user);
        }
    }
}

3.easyPOI

以上在导出导出excel、导出csv、word时代码有点过于繁琐,好消息是近两年在开发市场上流行一种简化POI开发的类库:easyPOI。从名称上就能发现就是为了简化开发。

能干什么?

Excel的快速导入导出,Excel模板导出,Word模板导出,可以仅仅5行代码就可以完成Excel的导入导出,修改导出格式简单粗暴,快速有效。

为谁而开发?

不太熟悉poi的
不想写太多重复太多的
只是简单的导入导出的
喜欢使用模板的
都可以使用easypoi

目标是什么?
Easypoi的目标不是替代poi,而是让一个不懂导入导出的快速使用poi完成Excel和word的各种操作,而不是看很多api才可以完成这样工作。

再次强调一下easyPOI完全替代不了POI!

3.1注解方式导出

    <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.1.0</version>
    </dependency>
属性类型类型说明
nameStringnull列名
needMergebooleanfasle纵向合并单元格
orderNumString“0”列的排序,支持name_id
replaceString[]{}值得替换 导出是{a_id,b_id} 导入反过来
savePathString“upload”导入文件保存路径
typeint1导出类型 1 是文本 2 是图片,3 是函数,10 是数字 默认是文本
widthdouble10列宽
heightdouble10列高,后期打算统一使用@ExcelTarget的height,这个会被废弃,注意
isStatisticsbooleanfasle自动统计数据,在追加一行统计,把所有数据都和输出这个处理会吞没异常,请注意这一点
isHyperlinkbooleanfalse超链接,如果是需要实现接口返回对象
isImportFieldbooleantrue校验字段,看看这个字段是不是导入的Excel中有,如果没有说明是错误的Excel,读取失败,支持name_id
exportFormatString“”导出的时间格式,以这个是否为空来判断是否需要格式化日期
importFormatString“”导入的时间格式,以这个是否为空来判断是否需要格式化日期
formatString“”时间格式,相当于同时设置了exportFormat 和 importFormat
databaseFormatString“yyyyMMddHHmmss”导出时间设置,如果字段是Date类型则不需要设置 数据库如果是string类型,这个需要设置这个数据库格式,用以转换时间格式输出
numFormatString“”数字格式化,参数是Pattern,使用的对象是DecimalFormat
imageTypeint1导出类型 1 从file读取 2 是从数据库中读取 默认是文件 同样导入也是一样的
suffixString“”文字后缀,如% 90 变成90%
isWrapbooleantrue是否换行 即支持\n
mergeRelyint[]{}合并单元格依赖关系,比如第二列合并是基于第一列 则{1}就可以了
mergeVerticalbooleanfasle纵向合并内容相同的单元格

    @Id
    @KeySql(useGeneratedKeys = true)
    @Excel(name = "编号", orderNum = "0", width = 5)
    private Long id;         //主键
    @Excel(name = "员工名", orderNum = "1", width = 15)
    private String userName; //员工名
    @Excel(name = "手机号", orderNum = "2", width = 15)
    private String phone;    //手机号
    @Excel(name = "省份名", orderNum = "3", width = 15)
    private String province; //省份名
    @Excel(name = "城市名", orderNum = "4", width = 15)
    private String city;     //城市名
    @Excel(name = "工资", orderNum = "5", width = 10, type = 10)
    private Integer salary;   // 工资
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "入职日期",  format = "yyyy-MM-dd",orderNum = "6", width = 15)
    private Date hireDate; // 入职日期
    private String deptId;   //部门id
    @Excel(name = "出生日期",  format = "yyyy-MM-dd",orderNum = "7", width = 15)
    private Date birthday; //出生日期
    @Excel(name = "照片", orderNum = "10",width = 15,type = 2)
    private String photo;    //一寸照片
    @Excel(name = "现在居住地址", orderNum = "9", width = 30)
    private String address;  //现在居住地址

    private List<Resource> resourceList; //办公用品
    public void downLoadXlsxWithEayPoi(HttpServletRequest request, HttpServletResponse response) throws IOException {
        List<User> users = userMapper.selectAll();
        //指定导出的格式是高版本的格式
        ExportParams exportParams = new ExportParams("员工信息", "数据", ExcelType.XSSF);
        //        直接使用EasyPOI提供的方法
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, User.class, users);
        String filename="员工信息.xlsx";
        //            设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);
    }

3.2注解方式导入

public class User {
    @Id
    @KeySql(useGeneratedKeys = true)
    @Excel(name = "编号", orderNum = "0", width = 5)
    private Long id;         //主键
    @Excel(name = "员工名", orderNum = "1", width = 15,isImportField="true")
    private String userName; //员工名
    @Excel(name = "手机号", orderNum = "2", width = 15,isImportField="true")
    private String phone;    //手机号
    @Excel(name = "省份名", orderNum = "3", width = 15,isImportField="true")
    private String province; //省份名
    @Excel(name = "城市名", orderNum = "4", width = 15,isImportField="true")
    private String city;     //城市名
    @Excel(name = "工资", orderNum = "5", width = 10, type=10, isImportField="true") //type=10表示会导出数字
    private Integer salary;   // 工资
    @JsonFormat(pattern="yyyy-MM-dd")
    @Excel(name = "入职日期",  format = "yyyy-MM-dd",orderNum = "6", width = 15,isImportField="true")
    private Date hireDate; // 入职日期
    private String deptId;   //部门id
    @Excel(name = "出生日期",  format = "yyyy-MM-dd",orderNum = "7", width = 15,isImportField="true")
    private Date birthday; //出生日期
    @Excel(name = "照片", orderNum = "10",width = 15,type = 2,isImportField="true",savePath = "/Users/liubo/IdeaProjects/workspace/src/main/resources/static/user_photos/")
    private String photo;    //一寸照片
    @Excel(name = "现在居住地址", orderNum = "9", width = 30,isImportField="true")
    private String address;  //现在居住地址

    private List<Resource> resourceList; //办公用品

}
    public void uploadExcleWithEasyPOI(MultipartFile file) throws Exception {
        ImportParams importParams = new ImportParams();
        importParams.setTitleRows(1); //有多少行的标题
        importParams.setHeadRows(1);//有多少行的头
        List<User> userList = ExcelImportUtil.importExcel(file.getInputStream(), User.class, importParams);

        for (User user : userList) {
            user.setId(null);
            userMapper.insert(user);
        }
    }

3.3结合模版导出Excel

    public void downLoadUserInfoWithEastPOI(Long id, HttpServletResponse response) throws IOException {

        //        获取模板的路径
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
        File templatePath = new File(rootPath.getAbsolutePath(), "/excel_template/userInfo3.xlsx");

        TemplateExportParams templateExportParams = new TemplateExportParams(templatePath.getPath(), true);
        User user = this.findById(id);
        Map<String, Object> map = EntityUtils.entityToMap(user);
        ImageEntity imageEntity = new ImageEntity();
        // 处理图片问题
        imageEntity.setUrl(rootPath + user.getPhoto());
        imageEntity.setRowspan(4); // 占多少行
        imageEntity.setColspan(2); // 占多少列

        map.put("photo", imageEntity);

        Workbook workbook = ExcelExportUtil.exportExcel(templateExportParams, map);

        //导出的文件名称
        String filename = "员工(" + user.getUserName() + ")详情信息.xlsx";
        //设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);
    }

3.4EasyPol导出CSV文件

 public void downLoadCSVWithEasyPOI(HttpServletResponse response) throws IOException {
        ServletOutputStream outputStream = response.getOutputStream();
//            文件名
        String filename = "百万数据.csv";
//            设置两个头 一个是文件的打开方式 一个是mime类型
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
        response.setContentType("application/csv");
        List<User> list = userMapper.selectAll();

        CsvExportParams params = new CsvExportParams();
        //        设置忽略的列
        params.setExclusions(new String[]{"照片"}); //这里写表头 中文
        CsvExportUtil.exportCsv(params, User.class, list, outputStream);
    }

CsvExportParams 的参数描述如下

属性类型默认值功能
encodingStringUTF8文件编码
spiltMarkString,分隔符
textMarkString字符串识别,可以去掉,需要前后一致
titleRowsint0表格头,忽略
headRowsint1标题
exclusionsString[]0忽略的字段

3.5EasyPol导出word

Word模板和Excel模板用法基本一致,支持的标签也是一致的,仅仅支持07版本的word也是只能生成后缀是docx的文档,poi对doc支持不好所以easyPOI中就没有支持doc,我们就拿docx做导出

这里得好好说说模板中标签的用法:

下面列举下EasyPoi支持的指令以及作用,最主要的就是各种fe的用法

三元运算 {{test ? obj:obj2}}
n: 表示 这个cell是数值类型 {{n:}}
le: 代表长度{{le:()}} 在if/else 运用{{le:() > 8 ? obj1 : obj2}}
fd: 格式化时间 {{fd:(obj;yyyy-MM-dd)}}
fn: 格式化数字 {{fn:(obj;###.00)}}
fe: 遍历数据,创建row
!fe: 遍历数据不创建row
$fe: 下移插入,把当前行,下面的行全部下移.size()行,然后插入
#fe: 横向遍历
v_fe: 横向遍历值
!if: 删除当前列 {{!if:(test)}}
单引号表示常量值 ‘’ 比如’1’ 那么输出的就是 1
&NULL& 空格
&INDEX& 表示循环中的序号,自动添加
]] 换行符 多行遍历导出
sum: 统计数据
    public void downloadContractWithEasyPOI(Long id, HttpServletResponse response) throws Exception {
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
        File templatePath = new File(rootPath.getAbsolutePath(), "/word_template/contract_template2.docx");

        User user = findById(id);
        Map<String, Object> params = new HashMap<>();
        params.put("userName", user.getUserName());
        params.put("hireDate", simpleDateFormat.format(user.getHireDate()));
        params.put("address", user.getAddress());

        // easyPOI无法为表格设置图片
        ImageEntity image = new ImageEntity();
        image.setHeight(180);
        image.setWidth(240);
        image.setUrl(rootPath.getPath() + user.getPhoto());
        params.put("photo", image);
        //        下面是表格中需要的数据
        List<Map> maplist = new ArrayList<>();
        Map<String, Object> map = null;
        for (Resource resource : user.getResourceList()) {
            map = new HashMap<String, Object>();
            map.put("name", resource.getName());
            map.put("price", resource.getPrice());
            map.put("needReturn", resource.getNeedReturn());
            maplist.add(map);
        }
        params.put("maplist", maplist);
        //        根据模板+数据 导出文档
        XWPFDocument xwpfDocument = WordExportUtil.exportWord07(templatePath.getPath(), params);
        String filename = user.getUserName() + "_合同.docx";
        //            设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        xwpfDocument.write(outputStream);
    }

4.PDF

4.1PDF简介

PDF(Portable Document Format的简称,意为“便携式文件格式”)是由Adobe Systems在1993年用于文件交换所发展出的文件格式。

PDF格式的文档的使用有如下好处:
1、跨平台
PDF文件格式与操作系统平台无关,也就是说,PDF文件不管是在Windows,Unix还是在苹果公司的Mac OS操作系统中都是通用的。不受平台的限制。越来越多的电子图书、产品说明、公司文告、网络资料、电子邮件开始使用PDF格式文件。

2、安全性高,不易修改
PDF是一种通用文件格式,不管创建源文档时使用的是哪些应用程序和平台,它均可以保留任何源文档的字体、图像、图形和版面设置。已成为世界上安全可靠地分发和交换电子文档及电子表单的实际标准。

3、阅读性能高,阅读舒适性好。

4、 相比Word格式的文档,PDF文件格式更为正式。
而WORD文档在跨平台使用方面不如PDF方便,而且WORD文档是可以进行编辑修改的,在安全性和可靠性上不如PDF,而且往往很难反映出用其它编辑软件排版的版面信息,使用上有一定的局限性。

所以,现在网站导出PDF也是比较普遍的,我们今天主要的课程就是学习如果导出PDF文件。

4.2Word转PDF

  • 把Word转成PDF目前最简单的方式就是调用office的方法,本质上就是打开Word后另存为成pdf,通过什么方式来调用呢?

    使用 jacob,速度上还是可以的,Word中的原样式也不会丢失

  • 最基本的环境:1、本机上安装了2007以上的office软件 2、jdk1.6以上的版本

下载链接: https://sourceforge.net/projects/jacob-project/files/jacob-project/

  • 第二步:把下载的资料中的jar放入到本地仓库

    这个jar从中央仓库中没有找到,需要我们自己打到本地仓库中

    进入到jar所在的目录执行以下命令:

    mvn install:install-file -DgroupId=com.jacob -DartifactId=jacob -Dversion=1.19 -Dfile=jacob.jar -Dpackaging=jar

  • 第三步:把dll文件放入到 jre\bin 目录下 64位的放x64文件,32位的放x86文件

    如果不太确定是32位还是64位的就把两个dll都放进去

<dependency>
    <groupId>com.jacob</groupId>
    <artifactId>jacob</artifactId>
    <version>1.9</version>
</dependency>
public class JacobDemo {
    public static void main(String[] args) {
        String source = "D:\\李四_合同.docx";
        String target = "D:\\李四_合同.pdf";
        System.out.println("Word转PDF开始启动...");
        ActiveXComponent app = null;
        try {
            //            调用window中的程序
            app = new ActiveXComponent("Word.Application");
            //            调用的时候不显示窗口
            app.setProperty("Visible", false);
            // 获得所有打开的文档
            Dispatch docs = app.getProperty("Documents").toDispatch();
            Dispatch doc = Dispatch.call(docs, "Open", source).toDispatch();
            System.out.println("转换文档到PDF:" + target);
            // 另存为,将文档保存为pdf,其中Word保存为pdf的格式宏的值是17
            Dispatch.call(doc, "SaveAs", target, 17);
            Dispatch.call(doc, "Close");
        } catch (Exception e) {
            System.out.println("Word转PDF出错:" + e.getMessage());
        } finally {
            // 关闭office
            if (app != null) {
                app.invoke("Quit", 0);
            }
        }
    }
}

4.3JasperReport导出PDF

  • JasperReport是一个强大、灵活的报表生成工具,能够展示丰富的页面内容,并将之转换成PDF,HTML,或者XML格式。该库完全由Java写成,可以用于在各种Java应用程序,包括J2EE,Web应用程序中生成动态内容。只需要将JasperReport引入工程中即可完成PDF报表的编译、显示、输出等工作。

    在开源的JAVA报表工具中,JasperReport发展是比较好的,比一些商业的报表引擎做得还好,如支持了十字交叉报表、统计报表、图形报表,支持多种报表格式的输出,如PDF、RTF、XML、CSV、XHTML、TEXT、DOCX以及OpenOffice。

    数据源支持更多,常用 JDBCSQL查询、XML文件、CSV文件、HQL(Hibernate查询),HBase,JAVA集合等。还允许你义自己的数据源,通过JASPER文件及数据源,JASPER就能生成最终用户想要的文档格式。

5.图表报表

1.JFreeChart

  • JFreeChart是JAVA平台上的一个开放的图表绘制类库。它完全使用JAVA语言编写,可生成饼图(pie charts)、柱状图(bar charts)、散点图(scatter plots)、时序图(time series)、甘特图(Gantt charts)等等多种图表,并且可以产生PNG和JPEG格式的输出,还可以与PDF或EXCEL关联。
       <!-- https://mvnrepository.com/artifact/org.jfree/jfreechart -->
        <dependency>
            <groupId>org.jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.0.19</version>
        </dependency>

1.饼状图

public class JFreeChartDemo1 {
    public static void main(String[] args) throws IOException {
        DefaultPieDataset dpd = new DefaultPieDataset();
        dpd.setValue("管理人员", 25);
        dpd.setValue("市场人员", 25);
        dpd.setValue("开发人员", 45);

        // 设置主题样式
        StandardChartTheme chartTheme = new StandardChartTheme("CN");
        // 设置标题字体
        chartTheme.setExtraLargeFont(new Font("华文宋体", Font.BOLD, 20));
        // 设置图例字体
        chartTheme.setRegularFont(new Font("华文宋体",Font.BOLD,15));
        // 设置轴向字体
        chartTheme.setLargeFont(new Font("华文宋体",Font.BOLD,15));
        // 应用样式
        ChartFactory.setChartTheme(chartTheme);
        // 标题,数据集,是否显示图列,是否显示提示,是否跳转
        JFreeChart pieChart = ChartFactory.createPieChart("CityInfoPort公司组织架构图", dpd, true, true, false);
        ChartUtilities.saveChartAsPNG(new File("/Users/liubo/1.png"),pieChart,400,300);

    }
}

2.折线图

public class JFreeChartDemo2 {
    public static void main(String[] args) throws IOException {
        DefaultCategoryDataset dpd = new DefaultCategoryDataset();
        dpd.addValue(2, "k1", "2011");
        dpd.setValue(8, "k1", "2012");
        dpd.setValue(11, "k1", "2013");

        dpd.addValue(12, "k2", "2011");
        dpd.setValue(4, "k2", "2012");
        dpd.setValue(30, "k2", "2013");

        // 设置主题样式
        StandardChartTheme chartTheme = new StandardChartTheme("CN");
        // 设置标题字体
        chartTheme.setExtraLargeFont(new Font("华文宋体", Font.BOLD, 20));
        // 设置图例字体
        chartTheme.setRegularFont(new Font("华文宋体", Font.BOLD, 15));
        // 设置轴向字体
        chartTheme.setLargeFont(new Font("华文宋体", Font.BOLD, 15));
        // 应用样式
        ChartFactory.setChartTheme(chartTheme);
        // 标题,数据集,是否显示图列,是否显示提示,是否跳转
        JFreeChart pieChart = ChartFactory.createLineChart("公司人数", "公司", "入职人数", dpd);
        ChartUtilities.saveChartAsPNG(new File("/Users/liubo/2.png"), pieChart, 400, 300);

    }
}

3.柱状图

public class JFreeChartDemo2 {
    public static void main(String[] args) throws IOException {
        DefaultCategoryDataset dpd = new DefaultCategoryDataset();
        dpd.addValue(2, "k1", "2011");
        dpd.setValue(8, "k1", "2012");
        dpd.setValue(11, "k1", "2013");

        dpd.addValue(12, "k2", "2011");
        dpd.setValue(4, "k2", "2012");
        dpd.setValue(30, "k2", "2013");

        // 设置主题样式
        StandardChartTheme chartTheme = new StandardChartTheme("CN");
        // 设置标题字体
        chartTheme.setExtraLargeFont(new Font("华文宋体", Font.BOLD, 20));
        // 设置图例字体
        chartTheme.setRegularFont(new Font("华文宋体", Font.BOLD, 15));
        // 设置轴向字体
        chartTheme.setLargeFont(new Font("华文宋体", Font.BOLD, 15));
        // 应用样式
        ChartFactory.setChartTheme(chartTheme);
        // 标题,数据集,是否显示图列,是否显示提示,是否跳转
        JFreeChart pieChart = ChartFactory.createBarChart("公司人数", "公司", "入职人数", dpd);
        ChartUtilities.saveChartAsPNG(new File("/Users/liubo/3.png"), pieChart, 400, 300);

    }
}

2.Highcharts

  • Highcharts 是一个用纯JavaScript编写的一个图表库, 能够很简单便捷的在web网站或是web应用程序添加有交互性的图表,并且免费提供给个人学习、个人网站和非商业用途使用。HighCharts支持的图表类型有曲线图、区域图、柱状图、饼状图、散状点图和综合图表。

  • 下载官网 https://www.highcharts.com.cn/download

3.Echarts

ECharts是由百度前端团队开发的一款开源的基于js图形报表组件,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖轻量级的矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

ECharts 特性:

丰富的可视化类型

多种数据格式无需转换直接使用

千万数据的前端展现

移动端优化

多渲染方案,跨平台使用!

深度的交互式数据探索

多维数据的支持以及丰富的视觉编码手段

动态数据

绚丽的特效

  • 1、下载js

    https://github.com/apache/incubator-echarts/tree/4.7.0

  • 或 从 npm 安装 (前提是电脑上有node环境)

    npm install echarts

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值