天天看點

使用Java導出Excel表格并由浏覽器直接下載下傳——基于POI架構背景描述技術準備導出Excel——嘗鮮版導出Excel——封裝版(通過反射)導出Excel——深度封裝(設定下拉選項) 擴充——多個列分别是不同的下拉選項怎麼封裝2019-10-28  更新,必看!!!2019-12-18更新,修複小機率的檔案名亂碼問題

目錄

背景描述

技術準備

導出Excel——嘗鮮版

導出Excel——封裝版(通過反射)

導出Excel——深度封裝(設定下拉選項)

 擴充——多個列分别是不同的下拉選項怎麼封裝

2019-10-28  更新,必看!!!

2019-12-18更新,修複小機率的檔案名亂碼問題

背景描述

最近部落客在做的Web項目中,有一個導出資料到Excel表格的需求,之前用純JS實作過,這次打算用Java在後端實作,将資料通過response以IO流的方式傳輸給前端,使浏覽器能直接下載下傳。這裡做一下記錄、筆記。

技術準備

我的項目是基于Spring Boot的,這裡隻貼出POI架構需要依賴的兩個包,其他的都無所謂,隻要能提供Controller讓浏覽器通路即可

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

導出Excel——嘗鮮版

這裡為了讓大家了解POI這個架構一系列的API,先以最low的方式去實作,稍後我們再進行封裝,達到“編寫一次、處處可用”。

我們隻需要提供一個Controller接口:

/**
     *  導出資料到Excel
     * @param response 響應體
     * 注意,我這裡是基于Spring Boot的,全局有一個@RestController注解,是以沒加@ResponseBody,
     * 如果你的不是,請加上@ResponseBody注解
     * */
    @GetMapping(value = "/out-excel-demo")
    public Object outExcelDemo(HttpServletResponse response) throws IOException {
        //建立HSSFWorkbook對象(excel的文檔對象)
        HSSFWorkbook wb = new HSSFWorkbook();
        //建立sheet對象(excel的表單)
        HSSFSheet sheet=wb.createSheet("sheet1");

        //建立第一行,這裡即是表頭。行的最小值是0,代表每一行,上限沒研究過,可參考官方的文檔
        HSSFRow row1=sheet.createRow(0);
        //在這一行建立單元格,并且将這個單元格的内容設為“賬号”,下面同理。
        //列的最小值辨別也是0
        row1.createCell(0).setCellValue("賬号");
        row1.createCell(1).setCellValue("使用者名");
        row1.createCell(2).setCellValue("日期");
        row1.createCell(3).setCellValue("是否完成");

        //第二行
        HSSFRow row2=sheet.createRow(1);
        row2.createCell(0).setCellValue("123456");
        row2.createCell(1).setCellValue("張三");
        row2.createCell(2).setCellValue("2019-08-05");
        row2.createCell(3).setCellValue("是");

        //第三行
        HSSFRow row3=sheet.createRow(2);
        row3.createCell(0).setCellValue("5681464");
        row3.createCell(1).setCellValue("李四");
        row3.createCell(2).setCellValue("2019-08-01");
        row3.createCell(3).setCellValue("否");

        //輸出Excel檔案
        OutputStream output=response.getOutputStream();
        response.reset();
        response
                .setHeader("Content-disposition", "attachment; filename=demo.xls");
        response.setContentType("application/x-xls");
        wb.write(output);
        output.close();
        return null;
    }
           

然後你可以在頁面上寫一個按鈕,點選的時候通過location.href指向上面的接口路徑,我這裡就省略了,看一下效果:

使用Java導出Excel表格并由浏覽器直接下載下傳——基于POI架構背景描述技術準備導出Excel——嘗鮮版導出Excel——封裝版(通過反射)導出Excel——深度封裝(設定下拉選項) 擴充——多個列分别是不同的下拉選項怎麼封裝2019-10-28  更新,必看!!!2019-12-18更新,修複小機率的檔案名亂碼問題

打開表格:

使用Java導出Excel表格并由浏覽器直接下載下傳——基于POI架構背景描述技術準備導出Excel——嘗鮮版導出Excel——封裝版(通過反射)導出Excel——深度封裝(設定下拉選項) 擴充——多個列分别是不同的下拉選項怎麼封裝2019-10-28  更新,必看!!!2019-12-18更新,修複小機率的檔案名亂碼問題

 到這裡,相信大家對POI有認識了吧?

其實它就是以每個HSSFRow為一個主體,每一個HSSFRow代表一行記錄,我們隻需要通過這個對象的createCell方法去建立單元格、指派就行,這樣就很清晰了吧?

導出Excel——封裝版(通過反射)

以上我們實作了簡單的資料導出,但是實際的場景根本不是這樣,我們都是從資料庫裡查出來資料,而且不可能這樣一行一行的去設定。

你肯定想到了循環,沒錯,循環是肯定的,但是僅僅循環還不夠靈活,為什麼呢?

根據面向對象的思維,我們可以将所有的表頭(即第一行)做成一個List集合參數,将所有的資料做成一個List集合參數,這個資料集合的泛型是我們的POJO實體類,然後我們兩個循環就能省略一大段代碼。

但是問題來了,我們例子中的導出表格,是“賬号、使用者名、日期、是否完成”這四個表頭,實體類也是對應的四個屬性。假如又來了一個導出需求呢?表頭不一樣了,所對應的實體類也不一樣了,難道我們再封裝成一個其他的方法?難道每個不同的Excel表結構都要封裝一個新的方法嗎?

做的時候部落客立馬就想到了反射機制,我們可以傳入List集合,對泛型不做限制,周遊資料集合的時候,通過反射得到這個對象的字段,動态指派。

但是這就有一個強制要求:在實體類聲明字段的時候,順序必須和表頭的前後順序一緻,否則循環周遊的時候會出現資料不對應的現象。

首先我們聲明一個實體類,這也符合我們真正的開發環境:

package com.dosion.smart.future.api.entity.activity.json;

import lombok.Data;

/**
 *  導出報名情況的資料傳輸對象
 * @author 秋楓豔夢
 * @date 2019-08-05
 * */
@Data
public class SignOutExcelJSON {
    //使用者賬号
    private String account;
    //使用者名
    private String username;
    //報名時間
    private String signDate;
    //是否完成
    private String finish;
}
           

然後封裝一個工具類出來:

package com.dosion.smart.future.utils;

import com.dosion.smart.future.api.entity.activity.json.SignOutExcelJSON;
import org.apache.poi.hssf.usermodel.HSSFCell;
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.ss.util.CellRangeAddress;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 *  資料導出excel,工具類
 * @author 秋楓豔夢
 * @date 209-08-05
 * */
public class ExcelUtil {

    /**
     *  生成Excel表格
     * @param sheetName sheet名稱
     * @param titleList 表頭清單
     * @param dataList 資料清單
     * @return HSSFWorkbook對象
     * */
    public static HSSFWorkbook createExcel(String sheetName,
                                           List<String> titleList,List dataList) throws IllegalAccessException {

        //建立HSSFWorkbook對象
        HSSFWorkbook wb = new HSSFWorkbook();
        //建立sheet對象
        HSSFSheet sheet=wb.createSheet(sheetName);
        //在sheet裡建立第一行,這裡即是表頭
        HSSFRow rowTitle=sheet.createRow(0);

        //寫入表頭的每一個列
        for (int i = 0; i < titleList.size(); i++) {
            //建立單元格
            rowTitle.createCell(i).setCellValue(titleList.get(i));
        }

        //寫入每一行的記錄
        for (int i = 0; i < dataList.size(); i++) {
            //建立新的一行,遞增
            HSSFRow rowData = sheet.createRow(i+1);

            //通過反射,擷取POJO對象
            Class cl = dataList.get(i).getClass();
            //擷取類的所有字段
            Field[] fields = cl.getDeclaredFields();
            for (int j = 0; j < fields.length; j++) {
                //設定字段可見,否則會報錯,禁止通路
                fields[j].setAccessible(true);
                //建立單元格
                rowData.createCell(j).setCellValue((String) fields[j].get(dataList.get(i)));
            }
        }
        return wb;
    }
}
           

然後我們模仿一下調用(這裡手動制造資料,真實情況下通過資料庫查詢):

/**
     *  導出Excel
     * 
     * 
     * */
    @GetMapping(value = "/out-excel-demo")
    public String outExcelDemo(HttpServletResponse response) throws IOException, IllegalAccessException {
        //檔案名
        String fileName = "活動報名情況一覽表";
        //sheet名
        String sheetName = "報名情況sheet";

        //表頭集合,作為表頭參數
        List<String> titleList = new ArrayList<>();
        titleList.add("使用者賬戶");
        titleList.add("使用者名");
        titleList.add("報名時間");
        titleList.add("是否完成");

        //資料對象,這裡模拟手動添加,真實的環境往往是從資料庫中得到
        SignOutExcelJSON excelJSON = new SignOutExcelJSON();
        excelJSON.setAccount("18210825916");
        excelJSON.setUsername("張三");
        excelJSON.setSignDate("2019-08-05");
        excelJSON.setFinish("是");
        SignOutExcelJSON excelJSON2 = new SignOutExcelJSON();
        excelJSON2.setAccount("15939305781");
        excelJSON2.setUsername("李四");
        excelJSON2.setSignDate("2019-08-01");
        excelJSON2.setFinish("否");
        //将兩個對象加入到集合中,作為資料參數
        List<SignOutExcelJSON> excelJSONList = new ArrayList<>();
        excelJSONList.add(excelJSON);
        excelJSONList.add(excelJSON2);

        //調取封裝的方法,傳入相應的參數
        HSSFWorkbook workbook = ExcelUtil.createExcel(sheetName,titleList, excelJSONList);

        //輸出Excel檔案
        OutputStream output=response.getOutputStream();
        response.reset();
        //中文名稱要進行編碼處理
        response
                .setHeader("Content-disposition", "attachment; filename="+new String(fileName.getBytes("GB2312"),"ISO8859-1")+".xls");
        response.setContentType("application/x-xls");
        workbook.write(output);
        output.close();
        return null;
    }
           

運作結果:

使用Java導出Excel表格并由浏覽器直接下載下傳——基于POI架構背景描述技術準備導出Excel——嘗鮮版導出Excel——封裝版(通過反射)導出Excel——深度封裝(設定下拉選項) 擴充——多個列分别是不同的下拉選項怎麼封裝2019-10-28  更新,必看!!!2019-12-18更新,修複小機率的檔案名亂碼問題
使用Java導出Excel表格并由浏覽器直接下載下傳——基于POI架構背景描述技術準備導出Excel——嘗鮮版導出Excel——封裝版(通過反射)導出Excel——深度封裝(設定下拉選項) 擴充——多個列分别是不同的下拉選項怎麼封裝2019-10-28  更新,必看!!!2019-12-18更新,修複小機率的檔案名亂碼問題

 效果是一樣的,而且很靈活。各位可以試一下,假如你導出其他子產品的資料,你隻需要傳入不同的表頭集合、實體類集合,就能實作你的需求,這也是封裝的魅力所在。

導出Excel——深度封裝(設定下拉選項)

假如我有一個需求:是否完成這一列隻能輸入是否,以下拉框的形式出現。

POI架構肯定有對應的API,大家看文檔也能學會,這裡我帶大家封裝一下,畢竟以可重用性為榮。

先封裝一個下拉條件對象:

package com.dosion.smart.future.api.entity.activity;

import lombok.Data;

/**
 *  導出Excel時的條件,有下列選項時使用
 * @author 秋楓豔夢
 * @date 2019-08-05
 * */
@Data
public class OutExcelQuery {
    //起始行
    private int rowStart;
    //結束行
    private int rowEnd;
    //起始列
    private int colStart;
    //結束列
    private int colEnd;
    //下拉參數
    private String[] params;

    //構造函數
    public OutExcelQuery(int rowStart,int rowEnd,int colStart,int colEnd,String[] params){
        this.rowStart = rowStart;
        this.rowEnd = rowEnd;
        this.colStart = colStart;
        this.colEnd = colEnd;
        this.params = params;
    }
}
           

再貼出來工具類:

package com.dosion.smart.future.utils;

import com.dosion.smart.future.api.entity.activity.OutExcelQuery;
import com.dosion.smart.future.api.entity.activity.json.SignOutExcelJSON;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 *  資料導出excel,工具類
 * @author 秋楓豔夢
 * @date 209-08-05
 * */
public class ExcelUtil {

    /**
     *  生成Excel表格
     * @param sheetName sheet名稱
     * @param titleList 表頭清單
     * @param dataList 資料清單
     * @param outExcelQuery 下拉選項設定
     * @return HSSFWorkbook對象
     * */
    public static HSSFWorkbook createExcel(String sheetName, List<String> titleList,
                                           List dataList, OutExcelQuery outExcelQuery) throws IllegalAccessException {

        //建立HSSFWorkbook對象(excel的文檔對象)
        HSSFWorkbook wb = new HSSFWorkbook();
        //建立sheet對象(excel的表單)
        HSSFSheet sheet=wb.createSheet(sheetName);
        //在sheet裡建立第一行,這裡即是表頭
        HSSFRow rowTitle=sheet.createRow(0);

        //寫入表頭的每一個列
        for (int i = 0; i < titleList.size(); i++) {
            //建立單元格
            rowTitle.createCell(i).setCellValue(titleList.get(i));
        }

        //寫入每一行的記錄
        int count = 0;
        for (int i = 0; i < dataList.size(); i++) {
            count++;
            //建立新的一行,遞增
            HSSFRow rowData = sheet.createRow(i+1);

            //通過反射,擷取POJO對象
            Class cl = dataList.get(i).getClass();
            //擷取類的所有字段
            Field[] fields = cl.getDeclaredFields();
            for (int j = 0; j < fields.length; j++) {
                //設定字段可見,否則會報錯,禁止通路
                fields[j].setAccessible(true);
                //建立單元格
                rowData.createCell(j).setCellValue((String) fields[j].get(dataList.get(i)));
            }
        }

        //如果開啟了下拉選項
        if (outExcelQuery!=null){
            //如果表格中的記錄數不是0
            if (count!=0){
                // 擷取下拉清單資料
                String[] strs = outExcelQuery.getParams();
                //設定哪些行的哪些列為下拉選項
                CellRangeAddressList rangeList =
                        new CellRangeAddressList(outExcelQuery.getRowStart(),
                                //結束行為-1時,說明設定所有行
                                outExcelQuery.getRowEnd()==-1?count:outExcelQuery.getRowEnd(),
                                outExcelQuery.getColStart(),outExcelQuery.getColEnd());
                //綁定下拉資料
                DVConstraint constraint = DVConstraint.createExplicitListConstraint(strs);
                //綁定兩者的關系
                HSSFDataValidation dataValidation = new HSSFDataValidation(rangeList,constraint);
                //添加到sheet中
                sheet.addValidationData(dataValidation);
            }
        }
        return wb;
    }
}
           

 如果我不想設定任何列為下拉選項,那我調用的時候将最後一個參數傳入null即可。如果想設定某一列或某幾列為下拉選項,那我調用的時候隻需要這樣(省略其他代碼):

String[] params = new String[]{"是","否"};
//從第一行開始,到最後一行結束,設定第4列為下拉選項
OutExcelQuery outExcelQuery = new OutExcelQuery(1,-1,3,3,params);
HSSFWorkbook workbook = ExcelUtil.createExcel(sheetName,titleList,activityService.outExcel(id),outExcelQuery);
           

 效果:

使用Java導出Excel表格并由浏覽器直接下載下傳——基于POI架構背景描述技術準備導出Excel——嘗鮮版導出Excel——封裝版(通過反射)導出Excel——深度封裝(設定下拉選項) 擴充——多個列分别是不同的下拉選項怎麼封裝2019-10-28  更新,必看!!!2019-12-18更新,修複小機率的檔案名亂碼問題

 擴充——多個列分别是不同的下拉選項怎麼封裝

以上下拉選項的封裝,隻是針對某一列或某幾列使用相同的下拉選項的情況,假如幾個資料列的下拉選項不同呢?

比如,再加一個性别列,下拉選項的值是男和女,此時一張Excel表中就出現了兩個下拉選項設定,該怎麼封裝?

部落客就不再寫了,留給大家思考,有疑問的可以留言。

提示一個思路:可以應用Java可選參數的特性,傳入多個OutExcelQuery對象,進行循環添加條件。

挖坑填坑,其樂融融。

2019-10-28  更新,必看!!!

最近有一位博友用到了我的這個工具類,首先表示很榮幸。

但是幫他解決問題的過程中,也發現了這個工具類的一點瑕疵,那就是:

之前的版本,必須要求表頭、實體類的字段一一對應,且順序要一緻,比如你導出的表格中有姓名和年齡兩個列,那麼你的實體類中隻能有name和age兩個字段,且順序要一緻,否則會出現年齡的值出現在姓名列的情況。

這樣确實有點不靈活,如果一個實體類有多個字段呢?如果依然采用這種方式,那我豈不是還要為導出表格專門寫一個實體類?

是以,我做了以下改進,在循環寫入每一行的列的時候,周遊的是titleList集合的長度,而不是實體類的字段數量,這樣一來,我們有一個表頭列,就會周遊出對象的幾個屬性,對象其他的屬性将不會展現到表格裡。舉個例子:

假設導出的表頭有姓名、年齡兩個列,但是實體類有name、age、sex三個字段,那麼我們周遊的是表頭的長度,即2,那麼sex這個字段将不會被寫入表格裡。這樣一來,實體類就可以有任意個字段了,隻需要保證前n個字段與表頭保持一緻即可。

需要注意的是,你需要導出的字段,在實體類裡仍然需要按照表頭的順序進行排列,沒辦法,隻能這麼取舍了,否則就做不到萬能了,當然大家也可以根據自己的業務去做定制化。

另外,此次增加了Excel的導入功能。

最後貼出來完整的工具類:

import com.dosion.model.activity.query.OutExcelQuery;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
 
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
/**
 *  資料導出、導入excel,工具類
 * @author 秋楓豔夢
 * @date 2019-10-28
 * */
public class ExcelUtil {
 
    /**
     *  生成Excel表格
     * @param sheetName sheet名稱
     * @param titleList 表頭清單
     * @param dataList 資料清單
     * @param outExcelQuery 下拉選項設定
     * @return HSSFWorkbook對象
     * */
    public static HSSFWorkbook createExcel(String sheetName, List<String> titleList,
                                           List dataList, OutExcelQuery outExcelQuery) throws IllegalAccessException {
 
        //建立HSSFWorkbook對象(excel的文檔對象)
        HSSFWorkbook wb = new HSSFWorkbook();
        //建立sheet對象(excel的表單)
        HSSFSheet sheet=wb.createSheet(sheetName);
        //在sheet裡建立第一行,這裡即是表頭
        HSSFRow rowTitle=sheet.createRow(0);
 
        //寫入表頭的每一個列
        for (int i = 0; i < titleList.size(); i++) {
            //建立單元格
            rowTitle.createCell(i).setCellValue(titleList.get(i));
        }
 
        int count = 0;
        //寫入每一行的記錄
        if (dataList!=null){
            for (int i = 0; i < dataList.size(); i++) {
                count++;
                //建立新的一行,遞增
                HSSFRow rowData = sheet.createRow(i+1);
 
                //通過反射,擷取POJO對象
                Class cl = dataList.get(i).getClass();
                //擷取類的所有字段
                Field[] fields = cl.getDeclaredFields();
                for (int j = 0; j < titleList.size(); j++) {
                    //設定字段可見,否則會報錯,禁止通路
                    fields[j].setAccessible(true);
                    //建立單元格
                    rowData.createCell(j).setCellValue((String) fields[j].get(dataList.get(i)));
                }
            }
        }
 
        //如果開啟了下拉選項
        if (outExcelQuery!=null){
            //如果表格中的記錄數不是0
            if (count!=0){
                // 擷取下拉清單資料
                String[] strs = outExcelQuery.getParams();
                //設定哪些行的哪些列為下拉選項
                CellRangeAddressList rangeList =
                        new CellRangeAddressList(outExcelQuery.getRowStart(),
                                //結束行為-1時,說明設定所有行
                                outExcelQuery.getRowEnd()==-1?count:outExcelQuery.getRowEnd(),
                                outExcelQuery.getColStart(),outExcelQuery.getColEnd());
                //綁定下拉資料
                DVConstraint constraint = DVConstraint.createExplicitListConstraint(strs);
                //綁定兩者的關系
                HSSFDataValidation dataValidation = new HSSFDataValidation(rangeList,constraint);
                //添加到sheet中
                sheet.addValidationData(dataValidation);
            }
        }
        return wb;
    }
 
    /**
     * 讀入excel檔案,解析後傳回
     * @param file
     * @throws IOException
     */
    public static List<String[]> readExcel(MultipartFile file) throws IOException{
        //檢查檔案
        checkFile(file);
        //獲得Workbook工作薄對象
        Workbook workbook = getWorkBook(file);
        //建立傳回對象,把每行中的值作為一個數組,所有行作為一個集合傳回
        List<String[]> list = new ArrayList<String[]>();
        if(workbook != null){
            for(int sheetNum = 0;sheetNum < workbook.getNumberOfSheets();sheetNum++){
                //獲得目前sheet工作表
                Sheet sheet = workbook.getSheetAt(sheetNum);
                if(sheet == null){
                    continue;
                }
                //獲得目前sheet的開始行
                int firstRowNum  = sheet.getFirstRowNum();
                //獲得目前sheet的結束行
                int lastRowNum = sheet.getLastRowNum();
                //循環除了第一行的所有行
                for(int rowNum = firstRowNum+1;rowNum <= lastRowNum;rowNum++){
                    //獲得目前行
                    Row row = sheet.getRow(rowNum);
                    if(row == null){
                        continue;
                    }
                    //獲得目前行的開始列
                    int firstCellNum = row.getFirstCellNum();
                    //獲得目前行的列數
                    int lastCellNum = row.getPhysicalNumberOfCells();
                    String[] cells = new String[row.getPhysicalNumberOfCells()];
                    //循環目前行
                    for(int cellNum = firstCellNum; cellNum < lastCellNum;cellNum++){
                        Cell cell = row.getCell(cellNum);
                        cells[cellNum] = getCellValue(cell);
                    }
                    list.add(cells);
                }
            }
            workbook.close();
        }
        return list;
    }
 
    /**
     * 檢查使用者上傳的檔案
     * @param file 檔案對象
     * */
    private static void checkFile(MultipartFile file) throws IOException{
        //判斷檔案是否存在
        if(null == file){
            throw new FileNotFoundException("檔案不存在!");
        }
        //獲得檔案名
        String fileName = file.getOriginalFilename();
        //判斷檔案是否是excel檔案
        if(!fileName.endsWith("xls") && !fileName.endsWith("xlsx")){
            throw new IOException(fileName + "不是excel檔案");
        }
    }
 
    /**
     *  擷取Workbook對象
     * @param file 檔案對象
     * @return Workbook對象
     * */
    private static Workbook getWorkBook(MultipartFile file) {
        //獲得檔案名
        String fileName = file.getOriginalFilename();
        //建立Workbook工作薄對象,表示整個excel
        Workbook workbook = null;
        try {
            //擷取excel檔案的io流
            InputStream is = file.getInputStream();
            //根據檔案字尾名不同(xls和xlsx)獲得不同的Workbook實作類對象
            if(fileName.endsWith("xls")){
                //2003
                workbook = new HSSFWorkbook(is);
            }else if(fileName.endsWith("xlsx")){
                //2007
                workbook = new XSSFWorkbook(is);
            }
        } catch (IOException e) {
 
        }
        return workbook;
    }
 
    /**
     *  擷取單元格的值
     * @param cell 單元格對象
     * @return 值
     * */
    private static String getCellValue(Cell cell){
        String cellValue = "";
        if(cell == null){
            return cellValue;
        }
        //把數字當成String來讀,避免出現1讀成1.0的情況
        if(cell.getCellType() == Cell.CELL_TYPE_NUMERIC){
            cell.setCellType(Cell.CELL_TYPE_STRING);
        }
        //判斷資料的類型
        switch (cell.getCellType()){
            case Cell.CELL_TYPE_NUMERIC: //數字
                cellValue = String.valueOf(cell.getNumericCellValue());
                break;
            case Cell.CELL_TYPE_STRING: //字元串
                cellValue = String.valueOf(cell.getStringCellValue());
                break;
            case Cell.CELL_TYPE_BOOLEAN: //Boolean
                cellValue = String.valueOf(cell.getBooleanCellValue());
                break;
            case Cell.CELL_TYPE_FORMULA: //公式
                cellValue = String.valueOf(cell.getCellFormula());
                break;
            case Cell.CELL_TYPE_BLANK: //空值
                cellValue = "未填寫";
                break;
            case Cell.CELL_TYPE_ERROR: //故障
                cellValue = "非法字元";
                break;
            default:
                cellValue = "未知類型";
                break;
        }
        return cellValue;
    }
}
           

2019-12-18更新,修複小機率的檔案名亂碼問題

最近有朋友反映說,他導出時有時候會出現檔案名亂碼的問題,部落客複現了很多次,都沒有複現出來。後來同僚也反映這個問題,部落客才重視起來。

經排查發現,首先之前的ISO8859-1,部落客寫的不規範,應該是ISO-8859-1(但是我感覺跟它沒關系,哈哈);其次,部落客把響應流的類型設定為了application/x-xls,這是标準的excel響應流格式。親測有效,無論是.xls字尾還是.xlsx字尾,都沒有問題。

在這裡要感謝這篇文章,總結了各種的response響應流格式:

https://blog.csdn.net/luman1991/article/details/53423305

貼出關鍵代碼部分:

//輸出Excel檔案
        OutputStream output=response.getOutputStream();
        response.reset();
        //中文名稱要進行編碼處理
        response
                .setHeader("Content-disposition", "attachment; filename="+new String("遊戲清單".getBytes("GB2312"),"ISO-8859-1")+".xlsx");
        response.setContentType("application/x-xls");
        workbook.write(output);
        output.close();
           

另外,我發現有一個奇怪的現象,大部分中文名都能相容,但是我把“遊戲清單”換成“學校”,那麼就會亂碼,換成“學校清單”就又好了……

有人說跟浏覽器的編碼方式有關,需要在後端代碼做處理,但是部落客試了一個遍,還是失敗。。。部落客就退而求其次吧,我覺得這是可以接受的,換一個同義的檔案名而已。

繼續閱讀