Excel導入導出——學習筆記

前言

  利用Poi進行Excel導入導出,在SpringBoot框架下進行測試,前端小小的使用了下Vue,並使用了Bootstrap-fileinput作爲前端上傳組件作爲參考,下載採用原生ajax下載形式,因爲一直忘記前端下載怎麼寫還有後端怎麼設置Request-Header,所以提供一下一整個前後端的思路,以後方便進行查閱,只是個人學習用,所以僅供參考。

引入POI依賴

        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>

ExcelUtil工具類

  excel的工具類,這裏基本上把導入導出的功能大致做了一遍,但還有值得改進的地方,不過想說的是,基本能用!

package com.podago.demo.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

/**
 * Excel工具類 - 提供導入導出功能
 * @author chenxiaotao
 */
public class ExcelUtil {
    private static final Logger logger = LoggerFactory.getLogger(ExcelUtil.class);

    private static final String XLS = "xls";

    private static final String XLSX = "xlsx";

    /**
     * 讀取Excel文件數據,並轉換爲二維數組形式輸出
     * @param file 文件流
     * @return Excel數據
     */
    public static List<List<Object>> readExcel(MultipartFile file) {
        Workbook workbook = null;
        String fileName = file.getOriginalFilename();
        InputStream in = null;
        try {
            in = file.getInputStream();
            if (fileName != null && fileName.endsWith(XLS)) {
                // 2003
                workbook = readExcel2003(in);
            } else if (fileName != null && fileName.endsWith(XLSX)) {
                // 2007
                workbook = readExcel2007(in);
            }
        } catch (IOException e) {
            logger.error("excel輸入流讀取錯誤,返回空列表", e);
            return Collections.emptyList();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    logger.error("excel輸入流關閉錯誤", e);
                }
            }
        }
        if (workbook == null) {
            return Collections.emptyList();
        }
        return readExcel(workbook);
    }


    /**
     * 讀取2007+版本的excel文件,即後綴爲xlsx
     */
    private static Workbook readExcel2007(InputStream in) {
        Workbook workbook = null;
        try {
            workbook = new XSSFWorkbook(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return workbook;
    }

    /**
     * 讀取2003版本的excel文件,即後綴爲xls
     */
    private static Workbook readExcel2003(InputStream in) {
        Workbook workbook = null;
        try {
            workbook = new HSSFWorkbook(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return workbook;
    }


    /**
     * 根據Workbook,讀取Excel數據
     * @param workbook excel工作簿
     * @return excel中數據
     */
    private static List<List<Object>> readExcel(Workbook workbook) {
        List<List<Object>> res = new ArrayList<>();
        Sheet sheet = workbook.getSheetAt(0);
        int lastRowNum = sheet.getLastRowNum();
        for (int i = 0; i <= lastRowNum; i++) {
            Row row = sheet.getRow(i);
            int lastCellNum = row.getLastCellNum();
            List<Object> rdata = new ArrayList<>(lastCellNum);
            for (int j = 0 ; j < lastCellNum; j++) {
                Cell cell = row.getCell(j);
                rdata.add(getCell(cell));
            }
            res.add(rdata);
        }
        return res;
    }

    /**
     * 獲取單元格數據
     * @param cell 單元格對象
     * @return 單元格中數據
     */
    private static Object getCell(Cell cell) {
        switch (cell.getCellTypeEnum()) {
            case NUMERIC:
                return cell.getNumericCellValue();
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return StringUtils.EMPTY;
            case ERROR:
                return StringUtils.EMPTY;
            case _NONE:
                return StringUtils.EMPTY;
            default:
                return null;
        }
    }

    /**
     * 輸出Excel數據流,並最終關閉流
     * @param dataLists 傳入數據
     * @param out 輸出流
     */
    public static void createExcel(List<List<Object>> dataLists, OutputStream out) {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFCellStyle titleCellStyle = workbook.createCellStyle();

        //設置單元標題樣式
        titleCellStyle.setAlignment(HorizontalAlignment.CENTER);
        titleCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        titleCellStyle.setFillForegroundColor(HSSFColorPredefined.SKY_BLUE.getIndex());
        titleCellStyle.setWrapText(true);
        //設置單元標題字體
        HSSFFont titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 13);
        titleCellStyle.setFont(titleFont);

        //設置表格內容單元樣式
        HSSFCellStyle valueCellStyle = workbook.createCellStyle();
        valueCellStyle.setAlignment(HorizontalAlignment.CENTER);
        valueCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        HSSFFont cellFont = workbook.createFont();
        cellFont.setFontHeightInPoints((short) 12);
        valueCellStyle.setFont(cellFont);

        // 二維形式
        for (int i = 0; i < dataLists.size(); i++) {
            HSSFRow row = sheet.createRow(i);
            List<Object> objects = dataLists.get(i);

            for (int j = 0; j < objects.size(); j++) {
                HSSFCell valueCell = row.createCell(j);
                valueCell.setCellStyle(valueCellStyle);
                valueCell.setCellValue(objects.get(j).toString());
            }
        }
        try {
            workbook.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //導出成功!
    }
}


分解讀取Excel文件的思路

  1. 獲取MultiPartFile文件,因爲是用的SpringBoot,所以直接傳入這個就可以獲取到文件名以及文件流,如果覺得和Spring有耦合的話可以將方法寫成 readExcel(String fileName, InputStream in)
  2. 判斷文件類型,簡單判斷文件後綴名,然後根據文件類型(因爲Excel2003和Excel2007+的讀取方式不同),調用不同的讀取方法,Excel2003是用的 HSSFWorkbook,Excel2007用的是XSSFWorkbook,獲取後賦值給Workbook引用,這裏利用了多態的思想。
  3. 獲取到Workbook後,我們便可以開始讀取數據
        /**
         * 根據Workbook,讀取Excel數據
         * @param workbook excel工作簿
         * @return excel中數據
         */
        private static List<List<Object>> readExcel(Workbook workbook) {
            List<List<Object>> res = new ArrayList<>();
            Sheet sheet = workbook.getSheetAt(0);
            int lastRowNum = sheet.getLastRowNum();
            for (int i = 0; i <= lastRowNum; i++) {
                Row row = sheet.getRow(i);
                int lastCellNum = row.getLastCellNum();
                List<Object> rdata = new ArrayList<>(lastCellNum);
                for (int j = 0 ; j < lastCellNum; j++) {
                    Cell cell = row.getCell(j);
                    rdata.add(getCell(cell));
                }
                res.add(rdata);
            }
            return res;
        }

    解釋下各個方法:

  • workbook.getSheetAt(index): 獲取指定下標下的sheet,sheet就是你打開Excel後右下角就可以看到這樣一個標識                

          

  主要是用於打印時區分紙張,一個Sheet表示一張紙,一般我們讀取第一個也就是下標爲0的一個,當然你可以自行對方法進行擴展,寫死總是不好的。

  • sheet.getLastRowNum(): 獲取該Sheet行數
  • row.getLastCellNum(): 獲取該行有多少單元格,即多少列

  瞭解這些方法後,我們就可以用遍歷二維數組的思想去遍歷每個單元格就行了,需要注意的是要根據單元格類型調用不同方法獲取單元格數據

   /**
     * 獲取單元格數據
     * @param cell 單元格對象
     * @return 單元格中數據
     */
    private static Object getCell(Cell cell) {
        switch (cell.getCellTypeEnum()) {
            case NUMERIC:
                return cell.getNumericCellValue();
            case STRING:
                return cell.getStringCellValue();
            case BOOLEAN:
                return cell.getBooleanCellValue();
            case FORMULA:
                return cell.getCellFormula();
            case BLANK:
                return StringUtils.EMPTY;
            case ERROR:
                return StringUtils.EMPTY;
            case _NONE:
                return StringUtils.EMPTY;
            default:
                return null;
        }
    }

分解導出Excel的思路

  1. 創建一個空的Workbook (工作簿)
  2. 創建一個空的Sheet
  3. 設置樣式
  4. 根據數據(依舊是二維數組的形式),來創建行,創建單元格
  5. 將工作簿內容寫入輸出流進行輸出,最後關閉流
   /**
     * 輸出Excel數據流,並最終關閉流
     * @param dataLists 傳入數據
     * @param out 輸出流
     */
    public static void createExcel(List<List<Object>> dataLists, OutputStream out) {
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFCellStyle titleCellStyle = workbook.createCellStyle();

        //設置單元標題樣式
        titleCellStyle.setAlignment(HorizontalAlignment.CENTER);
        titleCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        titleCellStyle.setFillForegroundColor(HSSFColorPredefined.SKY_BLUE.getIndex());
        titleCellStyle.setWrapText(true);
        //設置單元標題字體
        HSSFFont titleFont = workbook.createFont();
        titleFont.setFontHeightInPoints((short) 13);
        titleCellStyle.setFont(titleFont);

        //設置表格內容單元樣式
        HSSFCellStyle valueCellStyle = workbook.createCellStyle();
        valueCellStyle.setAlignment(HorizontalAlignment.CENTER);
        valueCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        HSSFFont cellFont = workbook.createFont();
        cellFont.setFontHeightInPoints((short) 12);
        valueCellStyle.setFont(cellFont);

        // 二維形式
        for (int i = 0; i < dataLists.size(); i++) {
            HSSFRow row = sheet.createRow(i);
            List<Object> objects = dataLists.get(i);

            for (int j = 0; j < objects.size(); j++) {
                HSSFCell valueCell = row.createCell(j);
                valueCell.setCellStyle(valueCellStyle);
                valueCell.setCellValue(objects.get(j).toString());
            }
        }
        try {
            workbook.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //導出成功!
    }

ExcelController

  控制層,主要記憶一下SpringMVC的上傳下載流程,還有下載文件時ResponseHeader的設置

package com.podago.demo.controller;

import com.podago.demo.utils.ExcelUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author chenxiaotao
 * @version 1.0
 * @date 2019/3/27 9:57
 **/
@RestController
@RequestMapping("/excel")
public class ExcelController {
    private static final String EXCEL2003_CONTENT_TYPE = "application/vnd.ms-excel";
    private static final String EXCEL2007_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public List<List<Object>> upload(@RequestPart MultipartFile excel) {
        String contentType = excel.getContentType();
        if (contentType.equals(EXCEL2003_CONTENT_TYPE) || contentType.equals(EXCEL2007_CONTENT_TYPE)) {
            return ExcelUtil.readExcel(excel);
        } else {
            return new ArrayList<>(0);
        }
    }

    @RequestMapping(value = "/download", method = RequestMethod.POST, produces = {"application/vnd.ms-excel;charset=UTF-8"})
    public void download(@RequestBody List<List<Object>> data, HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setHeader("Content-Disposition", "attachment;filename=excel.xls");
        ServletOutputStream outputStream = response.getOutputStream();
        ExcelUtil.createExcel(data, outputStream);
    }
}

前端上傳下載

  前端使用了boostrap-fileinput組件,這個組件是可以中文化的,並且感覺很強大,關於這個組件的使用這裏不過多描述,可自行查閱資料。

  前端實現的就是,把Excel導入後轉換爲JSON數據返回回來,把這段JSON數據傳回後端能夠導出爲Excel文件

  excel.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- Bootstrap-FileInput CSS -->    
    <link rel="stylesheet" href="dict/plugins/bootstrap-fileinput/css/fileinput.css">

</head>
<body>
    <div id="app" class="panel col-md-8">
        <input id="txt_file" name="excel" type="file" class="file" multiple
                 data-show-upload="true" data-show-caption="true" data-show-preview="false" data-upload-url="/excel/upload" data-allowed-file-extensions='["xlsx","xls"]'>
        <h5>excel文件內容:</h5>
        <textarea class="form-control" disabled rows="5">{{excel_data}}</textarea>
        <a @click="download" class="btn btn-primary">導出Excel</a>
    </div>
</body>
<script src="dict/plugins/jquery-3.3.1.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- Bootstrap-FileInput JS -->
<script src="dict/plugins/bootstrap-fileinput/js/fileinput.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="dict/excel/excel.js"></script>
</html>

  excel.js

  這裏的原生ajax異步下載可以多注意下,它異步獲取後 response 的 blob 後利用a標籤進行下載,我覺得是個挺好用的東西。

var app = new Vue({
   el: '#app',
   data: {
       excel_data: ''
   },methods:{
        download:function() {
        var url = '/excel/download';
        var xhr = new XMLHttpRequest();
        xhr.open('POST', url, true);        // 也可以使用POST方式,根據接口
        xhr.responseType = "blob";    // 返回類型blob
        xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8");
        // 定義請求完成的處理函數,請求前也可以增加加載框/禁用下載按鈕邏輯
        xhr.onload = function () {
            // 請求完成
            if (this.status === 200) {
                // 返回200
                var blob = this.response;
                var reader = new FileReader();
                var filename = this.getResponseHeader("Content-Disposition").substring(20);
                reader.readAsDataURL(blob);    // 轉換爲base64,可以直接放入a表情href
                reader.onload = function (e) {
                    // 轉換完成,創建一個a標籤用於下載
                    var a = document.createElement('a');
                    a.download = filename;
                    a.href = e.target.result;
                    $("body").append(a);    // 修復firefox中無法觸發click
                    a.click();
                    $(a).remove();
                }
            }
        };
        // 發送ajax請求
        xhr.send(JSON.stringify(app.excel_data))

}
    }, mounted: function() {
        //導入文件上傳完成之後的事件
        $("#txt_file").on("fileuploaded", function (event, data, previewId, index) {
            app.excel_data = data.response;
        });
   }
});

最後展示的頁面:

測試

測試用Excel:

在前端頁面選擇文件後上傳:

上傳成功

 

測試一下 下載 功能:

點擊導出Excel按鈕進行導出,彈出下載框下載,打開Excel內容進行對比可知下載成功!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章