java 利用poi根据excel模板导出数据(一)

前言

作为B端开发,导出数据是不可以避免的,但是有时候需求很变态,表头复杂的一笔,各种合并单元格,如下图:

 

 这些虽说用代码可以实现,但是很繁琐,而且代码并不能通用,遇到更复杂的更难受。为了追求更简单,高效率,我们需要换个方法-------利用模板导出数据!

?????能用模板?????

sure!

本文所用的excel样式,如果列更多、更复杂,直接套用就行  (下方图片中的数字是列名),行序号为1的是表格标题名称,行序号为2的是适应导出时把条件也导出来的情况,别怀疑,就是有这种需求!!

 

正文开始

1.需要用到的包

 poi

  <!-- json4excel,poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
            <version>1.3</version>
        </dependency>

nutz(工具包,非必须,公司框架,本文与之相关的就只有一个NutMap==Map<String,Object>,不用这个请手动替换)

   <!-- https://mvnrepository.com/artifact/org.nutz/nutz -->
        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutz</artifactId>
            <version>1.r.68.v20200427</version>
        </dependency>

2.直接上代码

  入口

    public static void doExportLongArrearsData() {
        // 要导出的数据
        NutMap nutMap = NutMap.NEW();
        nutMap.addv("comm","1111");
        nutMap.addv("a","1111");
        nutMap.addv("b","2222");
        nutMap.addv("c","11333311");
        nutMap.addv("d","1114441");
        nutMap.addv("e","555");
        nutMap.addv("f","6666");
        nutMap.addv("g","7777");
        nutMap.addv("h","88888");
        List<NutMap> list = Lists.newArrayList();
        list.add(nutMap);
        // 导出列  列数
        int colNum = 9;
        int[] colWidth = new int[colNum];
        for (int i = 0; i < colNum; i++) {
            colWidth[i] = 23;
        }
        // 从XX行开始为数据内容  excel 第一行为0
        int startRow = 5;
        //  2003版本的Excel (xls) ---- HSSFWorkbook
        //    2007版本以及更高版本 (xlsx)---- XSSFWorkbook
        XSSFWorkbook workbook = null;
        try {
            // 此处linux和windows通用   /files/cq.xlsx 在resource目录下  视情况而定
 
        /**
             *   特殊说明: this.getClass().getResourceAsStream 
             *   如果fileUrl路径前不加 / 那么会读取类文件夹下的文件。加了才会读取resource下面的文件
             *   exp: this.getClass().getResourceAsStream("/files/cq.xlsx") ==>读取resource下面的文件
             *   this.getClass().getResourceAsStream("files/cq.xlsx") ==>读取当前类下的文件
             *   源码:
             *     private String resolveName(String name) {
             *         if (name == null) {
             *             return name;
             *         }
             *         if (!name.startsWith("/")) {
             *             Class<?> c = this;
             *             while (c.isArray()) {
             *                 c = c.getComponentType();
             *             }
             *             String baseName = c.getName();
             *             int index = baseName.lastIndexOf('.');
             *             if (index != -1) {
             *                 name = baseName.substring(0, index).replace('.', '/')
             *                     +"/"+name;
             *             }
             *         } else {
             *             name = name.substring(1);
             *         }
             *         return name;
             *     }
             */
            //  InputStream inputStream = this.getClass().getResourceAsStream("/files/cq.xlsx");
            FileInputStream inputStream = new FileInputStream( new File("C:\\Users\\usaer\\Desktop\\buss.xlsx"));
            workbook = new XSSFWorkbook(inputStream);
 
            // 获取sheet
            XSSFSheet sheetAt = workbook.getSheetAt(0);
 
            // 动态列  修改表头名 、修改模板数据等操作
            // 自定义参数
            int k = 1;
            updateCellLoad(workbook,sheetAt,k);
 
            // 填充数据
            fillBodyData( sheetAt,startRow,list,colWidth);
 
            // 设置单元格宽度 (不设置的话,就是模板的宽度)
            if (null != colWidth) {
                for (int i = 0; i < colWidth.length; i++) {
                    sheetAt.setColumnWidth(i, colWidth[i] * 256 + 184);
                }
            }
 
            // 输出流
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            // excel工作空间写入流
            workbook.write(byteArrayOutputStream);
            InputStream wrap = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            // 写到本地
            writeToLocal("C:\\Users\\usaer\\Desktop\\buss_"+k+".xlsx",wrap);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            // 关闭流
            if (null != workbook)
                try {
                    workbook.close();
                }
                catch (IOException e) {
 
                }
        }
    }

修改列的方法 (填充数据之前之后都可以修改内容,包括但不限于修改列、修改行、样式等)

 
    public static void updateCellLoad( XSSFWorkbook workbook,Sheet sheet,int k) {
        try {
            //设置行 指定行 
            Row titlerow=sheet.getRow(2);
            //根据索引获取对应的列   如果合并单元格 有可能第一列就找不到  根据需求修改参数值
            Cell cell=titlerow.getCell(0, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
            //设置列的类型是字符串
            cell.setCellType(CellType.STRING);
            cell.setCellValue(k==1?"1":"0");
            String titleValue=cell.getStringCellValue();
            System.out.println(titleValue);
 
 
            // 设置行 指定行修改数据和样式 有可能第一行就找不到  根据需求修改参数值
            CellStyle style1 = workbook.createCellStyle();
            // 自动换行
            style1.setWrapText(true);
            Row row1 = sheet.createRow(1);
            // 行高度
            row1.setHeight(Short.parseShort("1000"));
            Cell row1Cell = row1.createCell(0, CellType.STRING);
            row1Cell.setCellStyle(style1);
            StringBuilder sb = new StringBuilder();
            sb.append("条件1:").append("ABC").append("\r\n");
            sb.append("条件2:").append("DEF").append("\r\n");
            sb.append("条件3:").append("GHI").append("\r\n");
            row1Cell.setCellValue(sb.toString());
 
        } catch (EncryptedDocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }

填充数据的方法

private static void fillBodyData(Sheet sheet, int startRow, List<NutMap> bodyList, int[] colWidth) {
 
        // 碰到 时间、金额等需要转换的  在setCellValue 里面操作
        for (int rowNum = 0; rowNum < bodyList.size(); rowNum++) {
            NutMap map = bodyList.get(rowNum);
            Row row = sheet.createRow(rowNum + startRow);
            int colNum = 0;
            // comm
            Cell accountNumberCell = row.createCell(colNum++, CellType.STRING);
            String accountNumber = map.getString("comm");
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(accountNumber));
            accountNumberCell.setCellValue(accountNumber);
            // a
            Cell accountNameCell = row.createCell(colNum++, CellType.STRING);
            String accountName = map.getString("a");
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(accountName));
            accountNameCell.setCellValue(accountName);
            // b
            Cell mobileCell = row.createCell(colNum++, CellType.STRING);
            String mobile = map.getString("b");
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(mobile));
            mobileCell.setCellValue(mobile);
 
            // c
            Cell addressCell = row.createCell(colNum++, CellType.STRING);
            String address = map.getString("c");
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(address));
            addressCell.setCellValue(address);
 
            // d
            Cell meterNoCell = row.createCell(colNum++, CellType.STRING);
            String meterNo = map.getString("d");
            meterNoCell.setCellValue(meterNo);
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(meterNo));
 
            // e
            Cell uncollectedNumberCell = row.createCell(colNum++, CellType.STRING);
            String uncollectedNumber = map.getString("e");
            uncollectedNumberCell.setCellValue(uncollectedNumber);
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(uncollectedNumber));
 
            // f
            Cell uncollectedDosageCell = row.createCell(colNum++, CellType.STRING);
            String uncollectedDosage = map.getString("f");
            uncollectedDosageCell.setCellValue(uncollectedDosage);
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(uncollectedDosage));
 
            // g
            Cell uncollectedDosageCell1 = row.createCell(colNum++, CellType.STRING);
            String uncollectedDosage1 = map.getString("g");
            uncollectedDosageCell1.setCellValue(uncollectedDosage1);
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(uncollectedDosage1));
 
            // h
            Cell uncollectedDosageCell2 = row.createCell(colNum++, CellType.STRING);
            String uncollectedDosage2 = map.getString("h");
            uncollectedDosageCell2.setCellValue(uncollectedDosage2);
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(uncollectedDosage2));
        }
    }

写到本地

 private static void writeToLocal(String destination, InputStream input)
            throws IOException {
        int index = 0;
        byte[] bytes = new byte[1024];
        FileOutputStream downloadFile = new FileOutputStream(destination);
        while ((index = input.read(bytes)) > 0) {
            downloadFile.write(bytes, 0, index);
        }
        downloadFile.close();
        input.close();
    }

代码就这些,纯测试,有些命名很随意,到时候用的时候自己改

效果图

注意: 

本文读取文件是从本地读取的,部署后,需要把模板文件放在resource下,利用代码中

 // 此处linux和windows通用   /files/cq.xlsx 在resource目录下  视情况而定
           //  InputStream inputStream = this.getClass().getResourceAsStream("/files/cq.xlsx");

来读取(本人框架非spring,spring自行解决),总之,获取到模板文件就是成功!!

如果想要设置单元格的样式比如字体垂直、水平居中等可以在  fillBodyData 中添加如下代码

 // 单元格样式
        XSSFCellStyle cellStyle = workbook.createCellStyle();
        // 设置单元格的水平对齐类型
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        // 设置单元格的垂直对齐类型
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

 然后再循环体内每个cell设置style ,例如:

 Cell accountNumberCell = row.createCell(colNum++, CellType.STRING);
            String accountNumber = map.getString("comm");
            colWidth[colNum - 1] = Math.max(colWidth[colNum - 1], Strings.charLength(accountNumber));
            accountNumberCell.setCellValue(accountNumber);
// 设置style
            accountNumberCell.setCellStyle(cellStyle);

 附加,如果springboot项目,获取项目目录下的模板,和直接输出到客户端,可参考:

@GetMapping("/exportTest4")
    //@ResponseBody
    public void exportTest4(HttpServletResponse response) {
//设置信息头,告诉浏览器内容为excel类型
        response.setHeader("content-Type", "application/vnd.ms-excel");
        //设置下载名称
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("展商模板.xlsx", StandardCharsets.UTF_8.name()));

//获取项目resources/excel目录下的excel模板文件
String REPORT_MONTH = ClassUtils.getDefaultClassLoader().getResource("").getPath() + "/excel/test2.xlsx";
        String path = REPORT_MONTH;
        try{
            System.out.println("path1=="+path);
            path = path.substring(1,path.length());
            path = java.net.URLDecoder.decode(path,"utf-8");
            System.out.println("path2=="+path);
        }catch (Exception e){
            e.printStackTrace();
        }

        String filename = "D://核酸检测信息.xls";
        try {
            // 输出到硬盘,所以要用到IO流,输出到fileName指定的路径上
            FileOutputStream fos = new FileOutputStream(filename);
            wb.write(fos);//写出
            wb.close();//关闭
            System.out.println("导出成功");
        } catch (Exception e) {
            //异常信息
            e.printStackTrace();
        }
}

如果是若依系统,也可以使用若依的通用下载方式下载:

直接返回文件名称,然后再页面通过ajax再次请求

return success(filename);