美文网首页
使用Apache poi 生成Excel文件

使用Apache poi 生成Excel文件

作者: EnjoyU | 来源:发表于2017-11-13 20:51 被阅读0次

    写在前面

    此为总结篇,很多都是参考网友总结的方法,然后在自己的项目中实践,感觉很实用,又不想存粹的拿来主义,现将原文链接附上以作记录和参考:
    1、http://blog.csdn.net/houxuehan/article/details/50960259
    2、http://www.cnblogs.com/wqsbk/p/5417180.html
    3、http://www.anyrt.com/blog/list/poiexcel.html
    4、http://poi.apache.org/spreadsheet/quick-guide.html 「官方」

    本文中我们将看到什么

    • Maven配置
    • 导出数据列表到Excel的公共方法
    • 工具类调用接口
    • Controller书写样例
    • 生成带有文档内部链接的Excel文档
    • 多任务通用模式下载任务解决方案,采用阻塞队列和线程池

    Maven

    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.16</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.16</version>
    </dependency>
    

    导出数据列表到Excel的公共方法

    /**
     * 导出excel公共方法,适合列表类文件导出。
     * 导出文件中的数值列支持选择计算统计。
     * 此方法为内存型导出,请注意数据量过大的问题
     * @param title
     * @param headMap
     * @param jsonArray
     * @param datePattern
     * @param colWidth
     * @param out
     */
    public static void exportExcelX(String title, Map<String, String> headMap, JSONArray jsonArray, 
                                    String datePattern, int colWidth, OutputStream out) {
        if (datePattern == null) datePattern = DEFAULT_DATE_PATTERN;
    
        // 声明一个工作薄
        SXSSFWorkbook workbook = new SXSSFWorkbook(1000);//缓存
        workbook.setCompressTempFiles(true);
    
        //表头样式
        CellStyle titleStyle = workbook.createCellStyle();
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        Font titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 20);
        titleFont.setBoldweight((short) 700);
        titleStyle.setFont(titleFont);
    
        // 列头样式
        CellStyle headerStyle = workbook.createCellStyle();
        //headerStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
        headerStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
        headerStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
        headerStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);
        headerStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        Font headerFont = workbook.createFont();
        headerFont.setFontHeightInPoints((short) 12);
        headerFont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
        headerStyle.setFont(headerFont);
    
        // 单元格样式
        CellStyle cellStyle = workbook.createCellStyle();
        //cellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
        cellStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
        cellStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
        cellStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);
        cellStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        Font cellFont = workbook.createFont();
        cellFont.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
        cellStyle.setFont(cellFont);
    
        // 生成一个(带标题)表格
        SXSSFSheet sheet = workbook.createSheet();
    
        //设置列宽
        int minBytes = colWidth < DEFAULT_COLOUMN_WIDTH ? DEFAULT_COLOUMN_WIDTH : colWidth;//至少字节数
        int[] arrColWidth = new int[headMap.size()];
    
        // 产生表格标题行,以及设置列宽
        String[] properties = new String[headMap.size()];
        String[] headers = new String[headMap.size()];
        int ii = 0;
        for (Iterator<String> iter = headMap.keySet().iterator(); iter.hasNext(); ) {
            String fieldName = iter.next();
    
            properties[ii] = fieldName;
            headers[ii] = headMap.get(fieldName);
    
            int bytes = fieldName.getBytes().length;
            arrColWidth[ii] = bytes < minBytes ? minBytes : bytes;
            sheet.setColumnWidth(ii, arrColWidth[ii] * 256);
            ii++;
        }
    
        // 遍历集合数据,产生数据行
        int rowIndex = 0;
        for (Object obj : jsonArray) {
            if (rowIndex == 65535 || rowIndex == 0) {
                if (rowIndex != 0) sheet = workbook.createSheet();//如果数据超过了,则在第二页显示
    
                SXSSFRow titleRow = sheet.createRow(0);//表头 rowIndex=0
                titleRow.createCell(0).setCellValue(title);
                titleRow.getCell(0).setCellStyle(titleStyle);
                sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headMap.size() - 1));
    
                SXSSFRow headerRow = sheet.createRow(1); //列头 rowIndex =1
                for (int i = 0; i < headers.length; i++) {
                    headerRow.createCell(i).setCellValue(headers[i]);
                    headerRow.getCell(i).setCellStyle(headerStyle);
    
                }
                rowIndex = 2;//数据内容从 rowIndex=2开始
            }
            JSONObject jo = (JSONObject) JSONObject.toJSON(obj);
            SXSSFRow dataRow = sheet.createRow(rowIndex);
            for (int i = 0; i < properties.length; i++) {
                SXSSFCell newCell = dataRow.createCell(i);
    
                Object o = jo.get(properties[i]);
                String cellValue = "";
                if (o == null) cellValue = "";
                else if (o instanceof Date) cellValue = new SimpleDateFormat(datePattern).format(o);
                else if (o instanceof Float || o instanceof Double) {
                    cellValue = new BigDecimal(o.toString()).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
                }
                else cellValue = o.toString();
    
                if (o instanceof Float || o instanceof Double || o instanceof BigDecimal) {
                    newCell.setCellValue(Double.parseDouble(cellValue));
                }else if(o instanceof Integer) {
                    newCell.setCellValue(Integer.parseInt(cellValue));
                }else{
                    newCell.setCellValue(cellValue);
                }
                newCell.setCellStyle(cellStyle);
            }
            rowIndex++;
        }
    
        try {
            workbook.write(out);
        } catch (IOException e) {
            log.error("导出Excel操作时组装Excel文件出现异常", e);
        } finally {
            if(workbook != null) {
                try {
                    workbook.close();
                    workbook.dispose();
                } catch (IOException e) {
                    log.error("关闭流时出现异常", e);
                }
            }
        }
    }
    

    以上方法适用于导出数据列表,提供文件大标题、列标题等,而且对数值列进行了专门的格式化处理,以方便需要后期选中统计处理。这种通用方法是内存型处理方式,需要注意查询的列表内容不能过大,可能上十万还能hold住,主要还是看各位的机器性能和内存容量,否则可能出现OOM,通常对于大文件的处理,建议分批查询再写入到excel中。excel文档可能存在sheet页行数不能超过65535的问题,对于这样的解决方案以上方法也已给出,就是扩展到另外一张sheet页。

    针对以上方法,我们给出一个使用样例,供大家参考使用。

    工具类调用接口

    /**
     * 导出文件到本地指定目录
     * @param title
     * @param fileName
     * @param headMap
     * @param ja
     */
    public static void downloadExcelFile(String title, String fileName, Map<String, String> headMap, JSONArray ja) {
        try {
            FileOutputStream fileOut = new FileOutputStream(fileName);
            ExcelUtil.exportExcelX(title, headMap, ja, null, 0, fileOut);
        } catch (Exception e) {
            log.error("处理导出文件时遇到异常", e);
        }
    }
    
    
    /**
     * 通过web调用方式直接导出Excel文件
     * @param title
     * @param headMap
     * @param ja
     * @param response
     */
    public static void downloadExcelFile(String title, Map<String, String> headMap, JSONArray ja, HttpServletResponse response) {
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ExcelUtil.exportExcelX(title, headMap, ja, null, 0, os);
            byte[] content = os.toByteArray();
            InputStream is = new ByteArrayInputStream(content);
            // 设置response参数,可以打开下载页面
            response.reset();
    
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + new String((title + ".xlsx").getBytes(), "iso-8859-1"));
            response.setContentLength(content.length);
            ServletOutputStream outputStream = response.getOutputStream();
            BufferedInputStream bis = new BufferedInputStream(is);
            BufferedOutputStream bos = new BufferedOutputStream(outputStream);
            byte[] buff = new byte[8192];
            int bytesRead;
            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
                bos.write(buff, 0, bytesRead);
    
            }
            
            // 自行在finally中处理流的关闭
            bis.close();
            bos.close();
            outputStream.flush();
            outputStream.close();
        } catch (Exception e) {
            log.error("导出Excel操作时操作下载请求出现异常", e);
        }
    }
    

    基于SpringBoot的Controller

    @RequestMapping("export")
    @ResponseBody
    public void exportOrderListData(HttpServletRequest request, HttpServletResponse response) {
    
        // 查询条件 【略】
        PageQuery pageQuery = new PageQuery();
    
        // 查询数据
        pageQuery.setPageSize(Integer.MAX_VALUE);
        orderInfoService.queryOrderLogInfoList(pageQuery);
    
        // 数据集合,将其转换成json串
        String jsonString = JSON.toJSONString(pageQuery.getList());
        JSONArray jsonArray = JSON.parseArray(jsonString);
    
        // 表头,key是获取数据时将要调用的get方法,value是显示的列标题
        Map<String, String> headMap = new LinkedHashMap<>();
        headMap.put("operationTime", "日期");
        headMap.put("buyerPhoneNum", "手机号");
        headMap.put("tradingNum", "交易ID");
        headMap.put("areaInfo", "区域信息");
        headMap.put("deviceNo", "设备编号");
        headMap.put("deviceAddr", "设备地址");
        headMap.put("cardSlotName", "货道名称");
        headMap.put("commId", "商品编号");
        headMap.put("commodityName", "商品名称");
        headMap.put("tradingPrice", "成本价");
        headMap.put("tradingPrice", "零售价");
        headMap.put("buyerPayPrice", "实际售价");
        headMap.put("empty", "代金券");
        headMap.put("empty", "大转盘奖励商品");
        headMap.put("empty", "随机立减金额");
    
        // 导出表格名称
        String title = "订单列表";
    
        // 执行导出
        ExcelUtil.downloadExcelFile(title, headMap, jsonArray, response);
    }
    

    在controller中直接调用导出文件到浏览器的接口,用户的浏览器将自动阻塞并等待文件下载,如果调用生成文件到本地的方法,则没有返回,直接在本地磁盘生成文件。需要注意的是,如果待导出文件很大,将会花费很长的计算和生成excel文档的时间,这可能会导致用户浏览器请求出现超时,影响用户体验,到时具体问题具体处理,你可以自己想办法规避这种情况,比如采用异步执行任务的方式,用户点击导出之后开启异步线程进行处理,然后直接返回提示信息,稍后将下载文件的链接给到用户即可。(下面有涉及到)

    Tips:如果想通过web调用直接导出Excel文件,那么在前端页面需要使用提交表单跳转的方式来请求下载,不能使用Ajax调用的方式,这点相信大家应该明了。

    比如,你可以写出这种通用的调用方式:

    function export(action){
        var form = $('#form');
        form.attr('action', action);
        form.attr('method', 'post');
        form.attr('target', '_self');
        form.submit();
    }
    

    到这里,相信你已经具备初步的导出Excel功能了,但是很多地方还需要根据自身情况进行优化,再有,以上编码难免有错误,请自行更正,相信都比较简单,而且估计导出Excel方法中很多方法都已过时,可以通过查看源码的方式,找到更新更优的方法。
    下面,我们给出一个针对复杂一点儿的业务(或者说场景)给出的一种文档方式。

    带有内部链接的Excel

    估计你也有遇到这种情况,就是导出的列表数据中,可能需要点击某个值,再链接到它关联的详情信息页面,这在excel中也是可以实现的,具体就是,通过点击某个单元格,然后链接到某个sheet页以显示更加详细的内容,同时,在详情页面上,额外增加一个「回到主页」的超链,这样整个文档就和在页面中点击差不多效果了。

    比如下面这种效果:


    列表页面
    详情页面

    基于SpringBoot的Controller中的实现方法

    @RequestMapping("exportFile")
    @ResponseBody
    public void exportExcelWithDetail(HttpServletRequest request, HttpServletResponse response) {
        PageQuery pageQuery = new PageQuery<>();
    
        // 查询条件 【略】
    
        log.info("开始执行导出商品销售数据信息Excel文件【START】");
        FileOutputStream fileOut = null;
        ByteArrayOutputStream os = new ByteArrayOutputStream();
    
        // 流式下载
        ServletOutputStream outputStream = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try{
            XSSFWorkbook wb = new XSSFWorkbook();
            CreationHelper createHelper = wb.getCreationHelper();
    
            // 创建主页
            XSSFSheet sheet = wb.createSheet("Home");
    
            // 创建页头
            XSSFRow row = sheet.createRow(0);
            XSSFCell cell = row.createCell((short) 0);
            cell.setCellValue("商品销售数据信息");
            sheet.addMergedRegion(new CellRangeAddress(
                    0, //first row (0-based)
                    0, //last row  (0-based)
                    0, //first column (0-based)
                    4  //last column  (0-based)
            ));
    
            // 创建页头样式
            // -- 字体
            XSSFFont font = wb.createFont();
            font.setFontHeightInPoints((short)24);
            font.setFontName("Microsoft YaHei UI");
            font.setBold(true);
    
            // -- 页头样式
            XSSFCellStyle style = wb.createCellStyle();
            style.setFont(font);
            style.setAlignment(HorizontalAlignment.CENTER);
            style.setBorderTop(BorderStyle.THIN);
            style.setTopBorderColor(IndexedColors.BLACK.getIndex());
            style.setBorderRight(BorderStyle.THIN);
            style.setRightBorderColor(IndexedColors.BLACK.getIndex());
            style.setBorderBottom(BorderStyle.THIN);
            style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
            style.setBorderLeft(BorderStyle.THIN);
            style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
    
            // -- 赋予页头样式
            cell.setCellStyle(style);
    
            // 创建标题栏
            XSSFRow titleRow = sheet.createRow(1);
            Cell title_cell_0 = titleRow.createCell((short) 0);
            title_cell_0.setCellValue("日期");
            Cell title_cell_1 = titleRow.createCell((short) 1);
            title_cell_1.setCellValue("区域信息");
            Cell title_cell_2 = titleRow.createCell((short) 2);
            title_cell_2.setCellValue("设备编号");
            Cell title_cell_3 = titleRow.createCell((short) 3);
            title_cell_3.setCellValue("商品销售总量");
            Cell title_cell_4 = titleRow.createCell((short) 4);
            title_cell_4.setCellValue("商品销售详情");
    
            // 设置单元格宽度
            int width = "商品销售详情".getBytes().length * 256;
            sheet.setColumnWidth(0, width);
            sheet.setColumnWidth(1, width);
            sheet.setColumnWidth(2, width);
            sheet.setColumnWidth(3, width);
            sheet.setColumnWidth(4, width);
    
            // -- 字体
            XSSFFont titleFont = wb.createFont();
            titleFont.setFontHeightInPoints((short)12);
            titleFont.setFontName("Microsoft YaHei UI");
            titleFont.setColor(new XSSFColor(new java.awt.Color(255, 255, 255)));
    
            // -- 标题样式
            XSSFCellStyle titleStyle = wb.createCellStyle();
            titleStyle.setFont(titleFont);
            titleStyle.setAlignment(HorizontalAlignment.LEFT);
            titleStyle.setFillBackgroundColor(new XSSFColor(new java.awt.Color(75, 102, 128)));
            titleStyle.setFillPattern(FillPatternType.FINE_DOTS);
            titleStyle.setBorderTop(BorderStyle.THIN);
            titleStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
            titleStyle.setBorderRight(BorderStyle.THIN);
            titleStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
            titleStyle.setBorderBottom(BorderStyle.THIN);
            titleStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
            titleStyle.setBorderLeft(BorderStyle.THIN);
            titleStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
    
            // 赋予标题栏样式
            title_cell_0.setCellStyle(titleStyle);
            title_cell_1.setCellStyle(titleStyle);
            title_cell_2.setCellStyle(titleStyle);
            title_cell_3.setCellStyle(titleStyle);
            title_cell_4.setCellStyle(titleStyle);
    
            // 超链接样式
            XSSFCellStyle hlink_style = wb.createCellStyle();
            Font hlink_font = wb.createFont();
            hlink_font.setUnderline(Font.U_SINGLE);
            hlink_font.setColor(IndexedColors.BLUE.getIndex());
            hlink_style.setFont(hlink_font);
            hlink_style.setAlignment(HorizontalAlignment.CENTER);
            hlink_style.setBorderTop(BorderStyle.THIN);
            hlink_style.setTopBorderColor(IndexedColors.BLACK.getIndex());
            hlink_style.setBorderRight(BorderStyle.THIN);
            hlink_style.setRightBorderColor(IndexedColors.BLACK.getIndex());
            hlink_style.setBorderBottom(BorderStyle.THIN);
            hlink_style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
            hlink_style.setBorderLeft(BorderStyle.THIN);
            hlink_style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
    
            // 内容样式-居左
            XSSFCellStyle contentStyle_left = wb.createCellStyle();
            contentStyle_left.setBorderTop(BorderStyle.THIN);
            contentStyle_left.setTopBorderColor(IndexedColors.BLACK.getIndex());
            contentStyle_left.setBorderRight(BorderStyle.THIN);
            contentStyle_left.setRightBorderColor(IndexedColors.BLACK.getIndex());
            contentStyle_left.setBorderBottom(BorderStyle.THIN);
            contentStyle_left.setBottomBorderColor(IndexedColors.BLACK.getIndex());
            contentStyle_left.setBorderLeft(BorderStyle.THIN);
            contentStyle_left.setLeftBorderColor(IndexedColors.BLACK.getIndex());
    
            // 内容样式-居中
            XSSFCellStyle contentStyle_center = wb.createCellStyle();
            contentStyle_center.setAlignment(HorizontalAlignment.CENTER);
            contentStyle_center.setBorderTop(BorderStyle.THIN);
            contentStyle_center.setTopBorderColor(IndexedColors.BLACK.getIndex());
            contentStyle_center.setBorderRight(BorderStyle.THIN);
            contentStyle_center.setRightBorderColor(IndexedColors.BLACK.getIndex());
            contentStyle_center.setBorderBottom(BorderStyle.THIN);
            contentStyle_center.setBottomBorderColor(IndexedColors.BLACK.getIndex());
            contentStyle_center.setBorderLeft(BorderStyle.THIN);
            contentStyle_center.setLeftBorderColor(IndexedColors.BLACK.getIndex());
    
            // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
            // 计数器
            long totalPage = 0l;
            long pageNum = 1;
            long count = Integer.MAX_VALUE;
    
            // 处理循环
            log.info("开始循环插入列表基本数据,并且开始查询销售数据详情信息。");
            do{
                // 分页查询数据
                PageQuery<TbDeviceInfo> pageQuery = new PageQuery();
                pageQuery.setPageNumber(pageNum++);
                pageQuery.setPageSize(10);
                exportQuery(request, pageQuery);
                totalPage = pageQuery.getTotalPage();
    
                // 保证某些功能只执行一次
                if(count > totalPage) {
                    count = totalPage;
                }
    
                // 搭建数据
                List<TbDeviceInfo> tbDeviceInfoList = pageQuery.getList();
                if(pageQuery.getTotalRow() > 0 && null != tbDeviceInfoList && !tbDeviceInfoList.isEmpty()) {
                    log.info("本次需要导出的记录数[同时需要执行该次数DB查询]:{}", pageQuery.getTotalRow());
    
                    for(int i=0, len=tbDeviceInfoList.size(); i < len; i++) {
                        TbDeviceInfo tbDeviceInfo = tbDeviceInfoList.get(i);
    
                        // 创建详情sheet页
                        String prefix = String.valueOf((totalPage - count) * pageQuery.getPageSize() + (i + 1)).concat("_");
                        XSSFSheet sheetDetail = wb.createSheet(prefix + tbDeviceInfo.getDeviceId());  // sheet页名称为设备ID
                        XSSFRow detailHeadRow = sheetDetail.createRow(0);
                        XSSFCell detailHeadCell = detailHeadRow.createCell((short)0);
                        detailHeadCell.setCellValue("商品销售数据详情");
                        detailHeadCell.setCellStyle(style);
                        sheetDetail.addMergedRegion(new CellRangeAddress(
                                0, //first row (0-based)
                                0, //last row  (0-based)
                                0, //first column (0-based)
                                4  //last column  (0-based)
                        ));
    
                        sheetDetail.setColumnWidth(0, width);
                        sheetDetail.setColumnWidth(1, width);
                        sheetDetail.setColumnWidth(2, width);
                        sheetDetail.setColumnWidth(3, width);
                        sheetDetail.setColumnWidth(4, width);
                        sheetDetail.setColumnWidth(5, width);
    
                        // 创建回到首页链接
                        XSSFCell toHomeCell = detailHeadRow.createCell((short)5);
                        toHomeCell.setCellValue("点击回到首页");
                        Hyperlink link2 = createHelper.createHyperlink(HyperlinkType.DOCUMENT);
                        link2.setAddress("'Home'!A1");
                        toHomeCell.setHyperlink(link2);
                        toHomeCell.setCellStyle(hlink_style);
    
                        // 创建详情公共信息
                        // 日期
                        XSSFRow dateRow = sheetDetail.createRow(1);
                        dateRow.createCell((short)0).setCellValue("日期");
                        dateRow.createCell((short)1).setCellValue(tbDeviceInfo.getOrderDate());
                        dateRow.getCell(0).setCellStyle(contentStyle_left);
                        dateRow.getCell(1).setCellStyle(contentStyle_left);
                        dateRow.createCell(2).setCellStyle(contentStyle_left);
                        dateRow.createCell(3).setCellStyle(contentStyle_left);
                        dateRow.createCell(4).setCellStyle(contentStyle_left);
                        sheetDetail.addMergedRegion(new CellRangeAddress(
                                1, //first row (0-based)
                                1, //last row  (0-based)
                                1, //first column (0-based)
                                4  //last column  (0-based)
                        ));
    
                        // 区域信息
                        XSSFRow areaRow = sheetDetail.createRow(2);
                        areaRow.createCell((short)0).setCellValue("区域信息");
                        areaRow.createCell((short)1).setCellValue(tbDeviceInfo.getAreaInfo());
                        areaRow.getCell(0).setCellStyle(contentStyle_left);
                        areaRow.getCell(1).setCellStyle(contentStyle_left);
                        areaRow.createCell(2).setCellStyle(contentStyle_left);
                        areaRow.createCell(3).setCellStyle(contentStyle_left);
                        areaRow.createCell(4).setCellStyle(contentStyle_left);
                        sheetDetail.addMergedRegion(new CellRangeAddress(
                                2, //first row (0-based)
                                2, //last row  (0-based)
                                1, //first column (0-based)
                                4  //last column  (0-based)
                        ));
    
                        // 设备编号
                        XSSFRow deviceNoRow = sheetDetail.createRow(3);
                        deviceNoRow.createCell((short)0).setCellValue("设备编号");
                        deviceNoRow.createCell((short)1).setCellValue(tbDeviceInfo.getDeviceId());
                        deviceNoRow.getCell(0).setCellStyle(contentStyle_left);
                        deviceNoRow.getCell(1).setCellStyle(contentStyle_left);
                        deviceNoRow.createCell(2).setCellStyle(contentStyle_left);
                        deviceNoRow.createCell(3).setCellStyle(contentStyle_left);
                        deviceNoRow.createCell(4).setCellStyle(contentStyle_left);
                        sheetDetail.addMergedRegion(new CellRangeAddress(
                                3, //first row (0-based)
                                3, //last row  (0-based)
                                1, //first column (0-based)
                                4  //last column  (0-based)
                        ));
    
                        // 设备地址
                        XSSFRow deviceAddrRow = sheetDetail.createRow(4);
                        deviceAddrRow.createCell((short)0).setCellValue("设备地址");
                        deviceAddrRow.createCell((short)1).setCellValue(tbDeviceInfo.getDeviceAddr());
                        deviceAddrRow.getCell(0).setCellStyle(contentStyle_left);
                        deviceAddrRow.getCell(1).setCellStyle(contentStyle_left);
                        deviceAddrRow.createCell(2).setCellStyle(contentStyle_left);
                        deviceAddrRow.createCell(3).setCellStyle(contentStyle_left);
                        deviceAddrRow.createCell(4).setCellStyle(contentStyle_left);
                        sheetDetail.addMergedRegion(new CellRangeAddress(
                                4, //first row (0-based)
                                4, //last row  (0-based)
                                1, //first column (0-based)
                                4  //last column  (0-based)
                        ));
    
                        // 销售详情列表标题
                        XSSFRow detailTitleRow = sheetDetail.createRow(5);
                        XSSFCell detailTitle_cell_0 = detailTitleRow.createCell((short)0);
                        detailTitle_cell_0.setCellValue("序号");
                        XSSFCell detailTitle_cell_1 = detailTitleRow.createCell((short)1);
                        detailTitle_cell_1.setCellValue("商品名称");
                        XSSFCell detailTitle_cell_2 = detailTitleRow.createCell((short)2);
                        detailTitle_cell_2.setCellValue("货道总容量");
                        XSSFCell detailTitle_cell_3 = detailTitleRow.createCell((short)3);
                        detailTitle_cell_3.setCellValue("单价");
                        XSSFCell detailTitle_cell_4 = detailTitleRow.createCell((short)4);
                        detailTitle_cell_4.setCellValue("销售数量");
    
                        // 设备标题样式
                        detailTitle_cell_0.setCellStyle(titleStyle);
                        detailTitle_cell_1.setCellStyle(titleStyle);
                        detailTitle_cell_2.setCellStyle(titleStyle);
                        detailTitle_cell_3.setCellStyle(titleStyle);
                        detailTitle_cell_4.setCellStyle(titleStyle);
    
                        // 查询数据库,补充详情数据
                        Integer deviceId = tbDeviceInfo.getId();
                        String orderDate = tbDeviceInfo.getOrderDate();
                        List<GoodsSaleDetailInfo> goodsSaleDetailInfoList = goodsSaleService.queryGoodsSaleDetails(deviceId, orderDate);
                        if(null != goodsSaleDetailInfoList && !goodsSaleDetailInfoList.isEmpty()) {
                            for(int j=0, len2=goodsSaleDetailInfoList.size(); j < len2; j++) {
                                GoodsSaleDetailInfo goodsSaleDetailInfo = goodsSaleDetailInfoList.get(j);
                                XSSFRow dataRow = sheetDetail.createRow(6 + j);
                                XSSFCell data_cell_0 = dataRow.createCell((short)0);
                                data_cell_0.setCellValue(j + 1);
                                XSSFCell data_cell_1 = dataRow.createCell((short)1);
                                data_cell_1.setCellValue(goodsSaleDetailInfo.getCommodityName());
                                XSSFCell data_cell_2 = dataRow.createCell((short)2);
                                data_cell_2.setCellValue(goodsSaleDetailInfo.getSlotCount());
                                XSSFCell data_cell_3 = dataRow.createCell((short)3);
                                data_cell_3.setCellValue(goodsSaleDetailInfo.getCommodityPrice() + "");
                                XSSFCell data_cell_4 = dataRow.createCell((short)4);
                                data_cell_4.setCellValue(goodsSaleDetailInfo.getSalesCount());
    
                                // 设置内容单元格样式
                                data_cell_0.setCellStyle(contentStyle_center);
                                data_cell_1.setCellStyle(contentStyle_left);
                                data_cell_2.setCellStyle(contentStyle_center);
                                data_cell_3.setCellStyle(contentStyle_center);
                                data_cell_4.setCellStyle(contentStyle_center);
                            }
                        }
    
                        // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    
                        // 创建主页内容栏
                        String index = String.valueOf(2 + (totalPage - count) * pageQuery.getPageSize() + i);
                        XSSFRow contentRow = sheet.createRow(Integer.valueOf(index));
                        XSSFCell content_cell_0 = contentRow.createCell((short) 0);
                        content_cell_0.setCellValue(tbDeviceInfo.getOrderDate());   // 日期
                        XSSFCell content_cell_1 = contentRow.createCell((short) 1);
                        content_cell_1.setCellValue(tbDeviceInfo.getAreaInfo());    // 区域信息
                        XSSFCell content_cell_2 = contentRow.createCell((short) 2);
                        content_cell_2.setCellValue(tbDeviceInfo.getDeviceId());    // 设备编号
                        XSSFCell content_cell_3 = contentRow.createCell((short) 3);
                        content_cell_3.setCellValue(tbDeviceInfo.getSalesCount());  // 销售数量
    
                        // 文件内链接内容
                        XSSFCell content_cell_4 = contentRow.createCell((short) 4);
                        content_cell_4.setCellValue("点击查看");
                        Hyperlink link2Detail = createHelper.createHyperlink(HyperlinkType.DOCUMENT);
                        link2Detail.setAddress("'" + prefix + tbDeviceInfo.getDeviceId() + "'!A1");
                        content_cell_4.setHyperlink(link2Detail);
                        content_cell_4.setCellStyle(hlink_style);
    
                        // 赋予内容单元格样式
                        content_cell_0.setCellStyle(contentStyle_center);
                        content_cell_1.setCellStyle(contentStyle_left);
                        content_cell_2.setCellStyle(contentStyle_left);
                        content_cell_3.setCellStyle(contentStyle_center);
                    }
    
                }else {
                    log.info("本次查询没有匹配到符合条件的记录,无记录导出。");
    
                    // 创建无数据内容的提示语
                    XSSFRow emptyRow = sheet.createRow(2);
                    XSSFCell emptyCell = emptyRow.createCell((short) 0);
                    emptyCell.setCellValue("没有查询到符合条件的记录");
                    sheet.addMergedRegion(new CellRangeAddress(
                            2, //first row (0-based)
                            2, //last row  (0-based)
                            0, //first column (0-based)
                            4  //last column  (0-based)
                    ));
    
                    XSSFCellStyle emptyStyle = wb.createCellStyle();
                    emptyStyle.setAlignment(HorizontalAlignment.CENTER);
                    emptyCell.setCellStyle(emptyStyle);
                }
    
            }while(--count > 0);
    
            // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    
            // 输出
            *//*String filename = DateUtil.getFormatDate(new Date(), "yyyyMMddHHmmssSSS") + "-workbook.xlsx";
            fileOut = new FileOutputStream(filename);
            wb.write(fileOut);*//*
            wb.write(os);
            wb.close();
    
            // 执行流下载
            byte[] content = os.toByteArray();
            InputStream is = new ByteArrayInputStream(content);
            // 设置response参数,可以打开下载页面
            response.reset();
    
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename="+ new String(("商品销售数据信息列表.xlsx").getBytes(), "iso-8859-1"));
            response.setContentLength(content.length);
            outputStream = response.getOutputStream();
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(outputStream);
            byte[] buff = new byte[8192];
            int bytesRead;
            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
                bos.write(buff, 0, bytesRead);
            }
            outputStream.flush();
        }catch (Exception e) {
            log.error("export excel error", e);
        }finally {
            // 流的关闭 【略】
        }
    }
    

    上面的代码篇幅有点儿大,方法没有经过抽取和优化,后续可以自行处理,详细逻辑还是很清晰的,加之以注释说明,很容易看懂。

    接下来,我们将针对项目中存在多个相同方式的下载任务,提供一种解决方案。

    将导出任务添加到队列中,并定时用线程清理队列中的任务,达到对服务器端减压的效果;同时,针对导出后的任务,以通知的形式告知用户导出任务已结束,可以到下载界面进行下载。
    这种解决方案的代码实现有很多种,下面将使用阻塞队列以及线程池的方式来处理,仅作参考。

    以下是基于SpringBoot的实现方式,代码中可能涉及到其他的技术标签,可忽略
    目前只能给出核心代码,实现思路已经很明确,很多实现细节可自行处理,也还有很多可优化之处

    Bean配置

    @Slf4j
    @Configuration
    public class ExportMethodConfiguration {
    
        @Bean(initMethod = "execute", destroyMethod = "shutdown")
        public ExportBlockingQueueService ExportBlockingQueueService(){
    
            // 创建保存文件的路径
            String filePath = "." + File.separator + "excel";
            File dir = new File(filePath);
            if(!dir.exists()){
                dir.mkdir();
                log.info("保存Excel文件的目录暂不存在,现已创建成功。{}", filePath);
            }
    
            return new ExportBlockingQueueServiceImpl();
        }
    }
    

    阻塞队列接口和实现

    /** 接口 */
    public interface ExportBlockingQueueService {
    
        void addTask(ExportExcelWrapper exportExcelService);
    
        void execute();
    
        void shutdown();
    
    }
    
    /** 实现 */
    @Slf4j
    public class ExportBlockingQueueServiceImpl implements ExportBlockingQueueService {
    
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        Queue<ExportExcelWrapper> linkedBlockingQueue = new ConcurrentLinkedQueue<>();
    
        @Override
        public void addTask(ExportExcelWrapper exportExcelService) {
            try {
                log.info("导出Excel任务已加入到执行队列中");
                linkedBlockingQueue.add(exportExcelService);
            } catch (Exception e) {
                log.error("将导出excel文件任务加入到队列时出现异常。", e);
            }
        }
    
        @Override
        public void shutdown() {
            scheduledExecutorService.shutdown();
        }
    
        public void execute() {
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    try {
                        log.info("-------- loop");
                        ExportExcelWrapper exportExcelWrapper = linkedBlockingQueue.poll();
                        if (null != exportExcelWrapper) {
                            log.info("开始执行下载任务");
                            exportExcelWrapper.getExportExcelService().executeQueryAndExport(exportExcelWrapper.getPageQuery(),
                                    exportExcelWrapper.getOperateUser(), exportExcelWrapper.getDownloadFilePath());
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, 3, 30, TimeUnit.SECONDS);
        }
    }
    

    导出任务包装类

    @Data
    public class ExportExcelWrapper {
    
        // 导出文件存放的相对路径
        private String downloadFilePath = "." + File.separator + "excel";
    
        // 登录用户
        private LoginUser operateUser;
    
        // 查询条件集合
        private PageQuery pageQuery;
    
        // 执行导出任务的实现方法
        private ExportExcelService exportExcelService;
    }
    

    导出Excel任务的公共接口与实现举例

    /** 接口声明 */
    public interface ExportExcelService {
    
        void executeQueryAndExport(PageQuery pageQuery, LoginUser operateUser, String downloadFilePath);
    
    }
    
    /** 实现举例 */
    @Service
    @Slf4j
    public class UserInfoExportExcelServiceImpl extends ExportExcelHelper implements ExportExcelService {
    
        @Autowired
        private OPUserService opUserService;
    
        @Override
        public void executeQueryAndExport(PageQuery pageQuery, LoginUser operateUser, String downloadFilePath) {
            // 查询数据
            pageQuery.setPageSize(Integer.MAX_VALUE);
            opUserService.queryUserPage(pageQuery);
    
            // 数据集合
            String jsonString = JSON.toJSONString(pageQuery.getList());
            JSONArray jsonArray = JSON.parseArray(jsonString);
    
            // 表头
            Map<String, String> headMap = new LinkedHashMap<>();
            headMap.put("loginName", "登录名称");
            headMap.put("userName", "用户名称");
            headMap.put("roleName", "用户角色");
            headMap.put("email", "Email");
            headMap.put("mobile", "联系电话");
            headMap.put("accessAuth", "授权准入");
            headMap.put("giveTime", "加入时间");
    
            // 导出表格名称
            String title = "用户信息列表";
            String fileName = DateUtil.getFormatDate(new Date(), "yyyyMMddHHmmssSSS") + title + ".xlsx";
            String filePath = downloadFilePath + File.separator + fileName;
    
            // 执行导出
            ExcelUtil.downloadExcelFile(title, filePath, headMap, jsonArray);
            log.info("导出Excel文件【{}】成功", fileName);
    
            // 保存下载记录
            createExportFileRecod(fileName, filePath, operateUser.getUserId());
            log.info("保存导出记录成功。");
    
            // 执行邮件发送通知
            sendEmail(operateUser, fileName, filePath);
        }
    }
    

    在Controller中调用

    @RequestMapping("exportFile")
    @ResponseBody
    public void queryUserExportDataList(HttpServletRequest request, HttpServletResponse response) {
        // 查询条件 【略】
        PageQuery pageQuery = new PageQuery<>();
    
        LoginUser loginUser = (LoginUser) redisExService.getModel(CacheKey.USER_LOGIN.getKey().concat(request.getSession().getId()));
        ExportExcelWrapper exportExcelWrapper =new ExportExcelWrapper();
        exportExcelWrapper.setExportExcelService(exportExcelService);
        exportExcelWrapper.setPageQuery(pageQuery);
        exportExcelWrapper.setOperateUser(loginUser);
        exportBlockingQueueService.addTask(exportExcelWrapper);
        
        // 可以在这里给用户回复提示语
        responseMsg(response);
    }
    

    好了,到目前为止,你基本上已经掌握了如何使用Java导出Excel文件了,当然,这里只是导出文件的基本方式,可能实际当中有各种不同各种奇怪的需求,这就需要自己根据实际场景去处理了。

    希望上面的知识对你的项目有所帮助。

    相关文章

      网友评论

          本文标题:使用Apache poi 生成Excel文件

          本文链接:https://www.haomeiwen.com/subject/dgynmxtx.html