使用Apache poi 生成Excel文件

寫在前面

此為總結(jié)篇礼预,很多都是參考網(wǎng)友總結(jié)的方法超营,然后在自己的項(xiàng)目中實(shí)踐鸳玩,感覺很實(shí)用,又不想存粹的拿來主義演闭,現(xiàn)將原文鏈接附上以作記錄和參考:
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配置
  • 導(dǎo)出數(shù)據(jù)列表到Excel的公共方法
  • 工具類調(diào)用接口
  • Controller書寫樣例
  • 生成帶有文檔內(nèi)部鏈接的Excel文檔
  • 多任務(wù)通用模式下載任務(wù)解決方案,采用阻塞隊(duì)列和線程池

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>

導(dǎo)出數(shù)據(jù)列表到Excel的公共方法

/**
 * 導(dǎo)出excel公共方法吕座,適合列表類文件導(dǎo)出虐译。
 * 導(dǎo)出文件中的數(shù)值列支持選擇計算統(tǒng)計。
 * 此方法為內(nèi)存型導(dǎo)出吴趴,請注意數(shù)據(jù)量過大的問題
 * @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);

    // 生成一個(帶標(biāo)題)表格
    SXSSFSheet sheet = workbook.createSheet();

    //設(shè)置列寬
    int minBytes = colWidth < DEFAULT_COLOUMN_WIDTH ? DEFAULT_COLOUMN_WIDTH : colWidth;//至少字節(jié)數(shù)
    int[] arrColWidth = new int[headMap.size()];

    // 產(chǎn)生表格標(biāo)題行,以及設(shè)置列寬
    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++;
    }

    // 遍歷集合數(shù)據(jù)漆诽,產(chǎn)生數(shù)據(jù)行
    int rowIndex = 0;
    for (Object obj : jsonArray) {
        if (rowIndex == 65535 || rowIndex == 0) {
            if (rowIndex != 0) sheet = workbook.createSheet();//如果數(shù)據(jù)超過了,則在第二頁顯示

            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;//數(shù)據(jù)內(nèi)容從 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("導(dǎo)出Excel操作時組裝Excel文件出現(xiàn)異常", e);
    } finally {
        if(workbook != null) {
            try {
                workbook.close();
                workbook.dispose();
            } catch (IOException e) {
                log.error("關(guān)閉流時出現(xiàn)異常", e);
            }
        }
    }
}

以上方法適用于導(dǎo)出數(shù)據(jù)列表锣枝,提供文件大標(biāo)題厢拭、列標(biāo)題等,而且對數(shù)值列進(jìn)行了專門的格式化處理撇叁,以方便需要后期選中統(tǒng)計處理供鸠。這種通用方法是內(nèi)存型處理方式,需要注意查詢的列表內(nèi)容不能過大陨闹,可能上十萬還能hold住回季,主要還是看各位的機(jī)器性能和內(nèi)存容量家制,否則可能出現(xiàn)OOM,通常對于大文件的處理泡一,建議分批查詢再寫入到excel中。excel文檔可能存在sheet頁行數(shù)不能超過65535的問題觅廓,對于這樣的解決方案以上方法也已給出鼻忠,就是擴(kuò)展到另外一張sheet頁。

針對以上方法杈绸,我們給出一個使用樣例帖蔓,供大家參考使用。

工具類調(diào)用接口

/**
 * 導(dǎo)出文件到本地指定目錄
 * @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("處理導(dǎo)出文件時遇到異常", e);
    }
}


/**
 * 通過web調(diào)用方式直接導(dǎo)出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);
        // 設(shè)置response參數(shù)瞳脓,可以打開下載頁面
        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中處理流的關(guān)閉
        bis.close();
        bos.close();
        outputStream.flush();
        outputStream.close();
    } catch (Exception e) {
        log.error("導(dǎo)出Excel操作時操作下載請求出現(xiàn)異常", e);
    }
}

基于SpringBoot的Controller

@RequestMapping("export")
@ResponseBody
public void exportOrderListData(HttpServletRequest request, HttpServletResponse response) {

    // 查詢條件 【略】
    PageQuery pageQuery = new PageQuery();

    // 查詢數(shù)據(jù)
    pageQuery.setPageSize(Integer.MAX_VALUE);
    orderInfoService.queryOrderLogInfoList(pageQuery);

    // 數(shù)據(jù)集合塑娇,將其轉(zhuǎn)換成json串
    String jsonString = JSON.toJSONString(pageQuery.getList());
    JSONArray jsonArray = JSON.parseArray(jsonString);

    // 表頭,key是獲取數(shù)據(jù)時將要調(diào)用的get方法劫侧,value是顯示的列標(biāo)題
    Map<String, String> headMap = new LinkedHashMap<>();
    headMap.put("operationTime", "日期");
    headMap.put("buyerPhoneNum", "手機(jī)號");
    headMap.put("tradingNum", "交易ID");
    headMap.put("areaInfo", "區(qū)域信息");
    headMap.put("deviceNo", "設(shè)備編號");
    headMap.put("deviceAddr", "設(shè)備地址");
    headMap.put("cardSlotName", "貨道名稱");
    headMap.put("commId", "商品編號");
    headMap.put("commodityName", "商品名稱");
    headMap.put("tradingPrice", "成本價");
    headMap.put("tradingPrice", "零售價");
    headMap.put("buyerPayPrice", "實(shí)際售價");
    headMap.put("empty", "代金券");
    headMap.put("empty", "大轉(zhuǎn)盤獎勵商品");
    headMap.put("empty", "隨機(jī)立減金額");

    // 導(dǎo)出表格名稱
    String title = "訂單列表";

    // 執(zhí)行導(dǎo)出
    ExcelUtil.downloadExcelFile(title, headMap, jsonArray, response);
}

在controller中直接調(diào)用導(dǎo)出文件到瀏覽器的接口埋酬,用戶的瀏覽器將自動阻塞并等待文件下載,如果調(diào)用生成文件到本地的方法烧栋,則沒有返回写妥,直接在本地磁盤生成文件。需要注意的是审姓,如果待導(dǎo)出文件很大珍特,將會花費(fèi)很長的計算和生成excel文檔的時間,這可能會導(dǎo)致用戶瀏覽器請求出現(xiàn)超時魔吐,影響用戶體驗(yàn)扎筒,到時具體問題具體處理,你可以自己想辦法規(guī)避這種情況酬姆,比如采用異步執(zhí)行任務(wù)的方式嗜桌,用戶點(diǎn)擊導(dǎo)出之后開啟異步線程進(jìn)行處理,然后直接返回提示信息轴踱,稍后將下載文件的鏈接給到用戶即可症脂。(下面有涉及到)

Tips:如果想通過web調(diào)用直接導(dǎo)出Excel文件,那么在前端頁面需要使用提交表單跳轉(zhuǎn)的方式來請求下載淫僻,不能使用Ajax調(diào)用的方式诱篷,這點(diǎn)相信大家應(yīng)該明了。

比如雳灵,你可以寫出這種通用的調(diào)用方式:

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

到這里棕所,相信你已經(jīng)具備初步的導(dǎo)出Excel功能了,但是很多地方還需要根據(jù)自身情況進(jìn)行優(yōu)化悯辙,再有琳省,以上編碼難免有錯誤迎吵,請自行更正,相信都比較簡單针贬,而且估計導(dǎo)出Excel方法中很多方法都已過時击费,可以通過查看源碼的方式,找到更新更優(yōu)的方法桦他。
下面蔫巩,我們給出一個針對復(fù)雜一點(diǎn)兒的業(yè)務(wù)(或者說場景)給出的一種文檔方式。

帶有內(nèi)部鏈接的Excel

估計你也有遇到這種情況快压,就是導(dǎo)出的列表數(shù)據(jù)中圆仔,可能需要點(diǎn)擊某個值,再鏈接到它關(guān)聯(lián)的詳情信息頁面蔫劣,這在excel中也是可以實(shí)現(xiàn)的坪郭,具體就是,通過點(diǎn)擊某個單元格脉幢,然后鏈接到某個sheet頁以顯示更加詳細(xì)的內(nèi)容歪沃,同時,在詳情頁面上鸵隧,額外增加一個「回到主頁」的超鏈绸罗,這樣整個文檔就和在頁面中點(diǎn)擊差不多效果了。

比如下面這種效果:


列表頁面

詳情頁面

基于SpringBoot的Controller中的實(shí)現(xiàn)方法

@RequestMapping("exportFile")
@ResponseBody
public void exportExcelWithDetail(HttpServletRequest request, HttpServletResponse response) {
    PageQuery pageQuery = new PageQuery<>();

    // 查詢條件 【略】

    log.info("開始執(zhí)行導(dǎo)出商品銷售數(shù)據(jù)信息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();

        // 創(chuàng)建主頁
        XSSFSheet sheet = wb.createSheet("Home");

        // 創(chuàng)建頁頭
        XSSFRow row = sheet.createRow(0);
        XSSFCell cell = row.createCell((short) 0);
        cell.setCellValue("商品銷售數(shù)據(jù)信息");
        sheet.addMergedRegion(new CellRangeAddress(
                0, //first row (0-based)
                0, //last row  (0-based)
                0, //first column (0-based)
                4  //last column  (0-based)
        ));

        // 創(chuàng)建頁頭樣式
        // -- 字體
        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);

        // 創(chuàng)建標(biāo)題欄
        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("區(qū)域信息");
        Cell title_cell_2 = titleRow.createCell((short) 2);
        title_cell_2.setCellValue("設(shè)備編號");
        Cell title_cell_3 = titleRow.createCell((short) 3);
        title_cell_3.setCellValue("商品銷售總量");
        Cell title_cell_4 = titleRow.createCell((short) 4);
        title_cell_4.setCellValue("商品銷售詳情");

        // 設(shè)置單元格寬度
        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)));

        // -- 標(biāo)題樣式
        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());

        // 賦予標(biāo)題欄樣式
        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());

        // 內(nèi)容樣式-居左
        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());

        // 內(nèi)容樣式-居中
        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());

        // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
        // 計數(shù)器
        long totalPage = 0l;
        long pageNum = 1;
        long count = Integer.MAX_VALUE;

        // 處理循環(huán)
        log.info("開始循環(huán)插入列表基本數(shù)據(jù)豆瘫,并且開始查詢銷售數(shù)據(jù)詳情信息珊蟀。");
        do{
            // 分頁查詢數(shù)據(jù)
            PageQuery<TbDeviceInfo> pageQuery = new PageQuery();
            pageQuery.setPageNumber(pageNum++);
            pageQuery.setPageSize(10);
            exportQuery(request, pageQuery);
            totalPage = pageQuery.getTotalPage();

            // 保證某些功能只執(zhí)行一次
            if(count > totalPage) {
                count = totalPage;
            }

            // 搭建數(shù)據(jù)
            List<TbDeviceInfo> tbDeviceInfoList = pageQuery.getList();
            if(pageQuery.getTotalRow() > 0 && null != tbDeviceInfoList && !tbDeviceInfoList.isEmpty()) {
                log.info("本次需要導(dǎo)出的記錄數(shù)[同時需要執(zhí)行該次數(shù)DB查詢]:{}", pageQuery.getTotalRow());

                for(int i=0, len=tbDeviceInfoList.size(); i < len; i++) {
                    TbDeviceInfo tbDeviceInfo = tbDeviceInfoList.get(i);

                    // 創(chuàng)建詳情sheet頁
                    String prefix = String.valueOf((totalPage - count) * pageQuery.getPageSize() + (i + 1)).concat("_");
                    XSSFSheet sheetDetail = wb.createSheet(prefix + tbDeviceInfo.getDeviceId());  // sheet頁名稱為設(shè)備ID
                    XSSFRow detailHeadRow = sheetDetail.createRow(0);
                    XSSFCell detailHeadCell = detailHeadRow.createCell((short)0);
                    detailHeadCell.setCellValue("商品銷售數(shù)據(jù)詳情");
                    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);

                    // 創(chuàng)建回到首頁鏈接
                    XSSFCell toHomeCell = detailHeadRow.createCell((short)5);
                    toHomeCell.setCellValue("點(diǎn)擊回到首頁");
                    Hyperlink link2 = createHelper.createHyperlink(HyperlinkType.DOCUMENT);
                    link2.setAddress("'Home'!A1");
                    toHomeCell.setHyperlink(link2);
                    toHomeCell.setCellStyle(hlink_style);

                    // 創(chuàng)建詳情公共信息
                    // 日期
                    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)
                    ));

                    // 區(qū)域信息
                    XSSFRow areaRow = sheetDetail.createRow(2);
                    areaRow.createCell((short)0).setCellValue("區(qū)域信息");
                    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)
                    ));

                    // 設(shè)備編號
                    XSSFRow deviceNoRow = sheetDetail.createRow(3);
                    deviceNoRow.createCell((short)0).setCellValue("設(shè)備編號");
                    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)
                    ));

                    // 設(shè)備地址
                    XSSFRow deviceAddrRow = sheetDetail.createRow(4);
                    deviceAddrRow.createCell((short)0).setCellValue("設(shè)備地址");
                    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)
                    ));

                    // 銷售詳情列表標(biāo)題
                    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("貨道總?cè)萘?);
                    XSSFCell detailTitle_cell_3 = detailTitleRow.createCell((short)3);
                    detailTitle_cell_3.setCellValue("單價");
                    XSSFCell detailTitle_cell_4 = detailTitleRow.createCell((short)4);
                    detailTitle_cell_4.setCellValue("銷售數(shù)量");

                    // 設(shè)備標(biāo)題樣式
                    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);

                    // 查詢數(shù)據(jù)庫,補(bǔ)充詳情數(shù)據(jù)
                    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());

                            // 設(shè)置內(nèi)容單元格樣式
                            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);
                        }
                    }

                    // -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

                    // 創(chuàng)建主頁內(nèi)容欄
                    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());    // 區(qū)域信息
                    XSSFCell content_cell_2 = contentRow.createCell((short) 2);
                    content_cell_2.setCellValue(tbDeviceInfo.getDeviceId());    // 設(shè)備編號
                    XSSFCell content_cell_3 = contentRow.createCell((short) 3);
                    content_cell_3.setCellValue(tbDeviceInfo.getSalesCount());  // 銷售數(shù)量

                    // 文件內(nèi)鏈接內(nèi)容
                    XSSFCell content_cell_4 = contentRow.createCell((short) 4);
                    content_cell_4.setCellValue("點(diǎn)擊查看");
                    Hyperlink link2Detail = createHelper.createHyperlink(HyperlinkType.DOCUMENT);
                    link2Detail.setAddress("'" + prefix + tbDeviceInfo.getDeviceId() + "'!A1");
                    content_cell_4.setHyperlink(link2Detail);
                    content_cell_4.setCellStyle(hlink_style);

                    // 賦予內(nèi)容單元格樣式
                    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("本次查詢沒有匹配到符合條件的記錄外驱,無記錄導(dǎo)出育灸。");

                // 創(chuàng)建無數(shù)據(jù)內(nèi)容的提示語
                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();

        // 執(zhí)行流下載
        byte[] content = os.toByteArray();
        InputStream is = new ByteArrayInputStream(content);
        // 設(shè)置response參數(shù),可以打開下載頁面
        response.reset();

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename="+ new String(("商品銷售數(shù)據(jù)信息列表.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 {
        // 流的關(guān)閉 【略】
    }
}

上面的代碼篇幅有點(diǎn)兒大昵宇,方法沒有經(jīng)過抽取和優(yōu)化磅崭,后續(xù)可以自行處理,詳細(xì)邏輯還是很清晰的瓦哎,加之以注釋說明砸喻,很容易看懂。

接下來蒋譬,我們將針對項(xiàng)目中存在多個相同方式的下載任務(wù)割岛,提供一種解決方案。

將導(dǎo)出任務(wù)添加到隊(duì)列中犯助,并定時用線程清理隊(duì)列中的任務(wù)癣漆,達(dá)到對服務(wù)器端減壓的效果;同時剂买,針對導(dǎo)出后的任務(wù)惠爽,以通知的形式告知用戶導(dǎo)出任務(wù)已結(jié)束癌蓖,可以到下載界面進(jìn)行下載。
這種解決方案的代碼實(shí)現(xiàn)有很多種婚肆,下面將使用阻塞隊(duì)列以及線程池的方式來處理租副,僅作參考。

以下是基于SpringBoot的實(shí)現(xiàn)方式旬痹,代碼中可能涉及到其他的技術(shù)標(biāo)簽附井,可忽略
目前只能給出核心代碼,實(shí)現(xiàn)思路已經(jīng)很明確两残,很多實(shí)現(xiàn)細(xì)節(jié)可自行處理,也還有很多可優(yōu)化之處

Bean配置

@Slf4j
@Configuration
public class ExportMethodConfiguration {

    @Bean(initMethod = "execute", destroyMethod = "shutdown")
    public ExportBlockingQueueService ExportBlockingQueueService(){

        // 創(chuàng)建保存文件的路徑
        String filePath = "." + File.separator + "excel";
        File dir = new File(filePath);
        if(!dir.exists()){
            dir.mkdir();
            log.info("保存Excel文件的目錄暫不存在把跨,現(xiàn)已創(chuàng)建成功人弓。{}", filePath);
        }

        return new ExportBlockingQueueServiceImpl();
    }
}

阻塞隊(duì)列接口和實(shí)現(xiàn)

/** 接口 */
public interface ExportBlockingQueueService {

    void addTask(ExportExcelWrapper exportExcelService);

    void execute();

    void shutdown();

}

/** 實(shí)現(xiàn) */
@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("導(dǎo)出Excel任務(wù)已加入到執(zhí)行隊(duì)列中");
            linkedBlockingQueue.add(exportExcelService);
        } catch (Exception e) {
            log.error("將導(dǎo)出excel文件任務(wù)加入到隊(duì)列時出現(xiàn)異常。", 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("開始執(zhí)行下載任務(wù)");
                        exportExcelWrapper.getExportExcelService().executeQueryAndExport(exportExcelWrapper.getPageQuery(),
                                exportExcelWrapper.getOperateUser(), exportExcelWrapper.getDownloadFilePath());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 3, 30, TimeUnit.SECONDS);
    }
}

導(dǎo)出任務(wù)包裝類

@Data
public class ExportExcelWrapper {

    // 導(dǎo)出文件存放的相對路徑
    private String downloadFilePath = "." + File.separator + "excel";

    // 登錄用戶
    private LoginUser operateUser;

    // 查詢條件集合
    private PageQuery pageQuery;

    // 執(zhí)行導(dǎo)出任務(wù)的實(shí)現(xiàn)方法
    private ExportExcelService exportExcelService;
}

導(dǎo)出Excel任務(wù)的公共接口與實(shí)現(xiàn)舉例

/** 接口聲明 */
public interface ExportExcelService {

    void executeQueryAndExport(PageQuery pageQuery, LoginUser operateUser, String downloadFilePath);

}

/** 實(shí)現(xiàn)舉例 */
@Service
@Slf4j
public class UserInfoExportExcelServiceImpl extends ExportExcelHelper implements ExportExcelService {

    @Autowired
    private OPUserService opUserService;

    @Override
    public void executeQueryAndExport(PageQuery pageQuery, LoginUser operateUser, String downloadFilePath) {
        // 查詢數(shù)據(jù)
        pageQuery.setPageSize(Integer.MAX_VALUE);
        opUserService.queryUserPage(pageQuery);

        // 數(shù)據(jù)集合
        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", "聯(lián)系電話");
        headMap.put("accessAuth", "授權(quán)準(zhǔn)入");
        headMap.put("giveTime", "加入時間");

        // 導(dǎo)出表格名稱
        String title = "用戶信息列表";
        String fileName = DateUtil.getFormatDate(new Date(), "yyyyMMddHHmmssSSS") + title + ".xlsx";
        String filePath = downloadFilePath + File.separator + fileName;

        // 執(zhí)行導(dǎo)出
        ExcelUtil.downloadExcelFile(title, filePath, headMap, jsonArray);
        log.info("導(dǎo)出Excel文件【{}】成功", fileName);

        // 保存下載記錄
        createExportFileRecod(fileName, filePath, operateUser.getUserId());
        log.info("保存導(dǎo)出記錄成功着逐。");

        // 執(zhí)行郵件發(fā)送通知
        sendEmail(operateUser, fileName, filePath);
    }
}

在Controller中調(diào)用

@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);
    
    // 可以在這里給用戶回復(fù)提示語
    responseMsg(response);
}

好了崔赌,到目前為止,你基本上已經(jīng)掌握了如何使用Java導(dǎo)出Excel文件了耸别,當(dāng)然健芭,這里只是導(dǎo)出文件的基本方式,可能實(shí)際當(dāng)中有各種不同各種奇怪的需求秀姐,這就需要自己根據(jù)實(shí)際場景去處理了慈迈。

希望上面的知識對你的項(xiàng)目有所幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末省有,一起剝皮案震驚了整個濱河市痒留,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蠢沿,老刑警劉巖伸头,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舷蟀,居然都是意外死亡恤磷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門野宜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扫步,“玉大人,你說我怎么就攤上這事速缨⌒科蓿” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵旬牲,是天一觀的道長仿粹。 經(jīng)常有香客問我搁吓,道長,這世上最難降的妖魔是什么吭历? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任堕仔,我火速辦了婚禮,結(jié)果婚禮上晌区,老公的妹妹穿的比我還像新娘摩骨。我一直安慰自己,他們只是感情好朗若,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布恼五。 她就那樣靜靜地躺著,像睡著了一般哭懈。 火紅的嫁衣襯著肌膚如雪灾馒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天遣总,我揣著相機(jī)與錄音睬罗,去河邊找鬼。 笑死旭斥,一個胖子當(dāng)著我的面吹牛容达,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播垂券,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼花盐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了圆米?” 一聲冷哼從身側(cè)響起卒暂,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娄帖,沒想到半個月后也祠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡近速,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年诈嘿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片削葱。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奖亚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出析砸,到底是詐尸還是另有隱情昔字,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站作郭,受9級特大地震影響陨囊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夹攒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一蜘醋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咏尝,春花似錦压语、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至允懂,卻和暖如春斥季,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背累驮。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舵揭,地道東北人谤专。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像午绳,于是被迫代替她去往敵國和親置侍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容