Kymotz's Blog

Apache POI|Excel读取导入、解析工具类

#教程 #工具

操作 Excel 设置单元格样式是比较繁琐的,还有导入导出是设置的 Header 信息,为了简化这些操实现了如下工具类。工具类具有设置单元格的基本背景颜色、字体、字号、字体颜色、边框等,还实现了导入导出、磁盘读写 Excel。

这个工具类来源于我们项目的实践,简化了一些重复性的工作,欢迎尝试,欢迎提出问题。

预览 & 功能

1.设置简单的单元格样式。如字体、字号、对齐方式、颜色、边框

image.png

image.png

2.便捷实现下拉框

image.png

3.提供一组默认属性方便设置行高和字体

4.提供上传文件的读取(读取 Request 流)、下载文件导出(导出到 Response 流)、指定磁盘文件导出、指定磁盘文件读取

5.Excel 数据解析为 Java List

实现

导出一个 excel 大致的代码

这是使用工具类来完成一个表格的生成到导出。

 1    // 导出赛事
 2    public HSSFWorkbook downloadTemplate(Long competitionId, HttpServletResponse response) {
 3        // work book
 4        HSSFWorkbook workbook = new HSSFWorkbook();
 5        String sheetName = "导入模板";
 6
 7        // sheet
 8        HSSFSheet sheet = workbook.createSheet(sheetName);
 9
10        // 默认高度 16像素
11        sheet.setDefaultRowHeightInPoints(16F);
12
13        //设置指定列宽
14        sheet.setColumnWidth(0, 14 * 256);
15        sheet.setColumnWidth(1, 14 * 256);
16        sheet.setColumnWidth(2, 16 * 256);
17        sheet.setColumnWidth(3, 16 * 256);
18        sheet.setColumnWidth(4, 32 * 256);
19        sheet.setColumnWidth(5, 64 * 256);
20
21	// 单元格样式
22        HSSFCellStyle testStyle = new ExcelUtil.HSSFStyleBuilder()
23                .createStyle(workbook)
24                .font("宋体", (short) 11, true)
25                .colorRed()
26                .alignCenter()
27                .backgroundBlue()
28                .build();
29
30	// 单元格样式
31        HSSFCellStyle tipStyle = new ExcelUtil.HSSFStyleBuilder()
32                .createStyle(workbook)
33                .font("宋体", (short) 11, true)
34                .horizontalAlignLeft()
35                .verticalAlignCenter()
36                .wrapText()
37                .build();
38
39	// 单元格样式
40        HSSFCellStyle titleRedStyle = new ExcelUtil.HSSFStyleBuilder()
41                .createStyle(workbook)
42                .font("宋体", (short)11, true)
43                .colorRed()
44                .alignCenter()
45                .wrapText()
46                .build();
47
48	// 单元格样式
49        HSSFCellStyle titleBlackStyle = new ExcelUtil.HSSFStyleBuilder()
50                .createStyle(workbook)
51                .font("宋体", (short)11, true)
52                .colorBlack()
53                .alignCenter()
54                .wrapText()
55                .build();
56
57        // 设置单元格合并
58        CellRangeAddress rowRegion = new CellRangeAddress(0, 0, 0, 5);
59        sheet.addMergedRegion(rowRegion);
60
61        // 第一行 row=0
62        HSSFRow row = sheet.createRow(0);
63
64        Competition competition = adminCompetitionMapper.selectById(competitionId);
65
66        row.setHeightInPoints(110);
67        HSSFCell cell = row.createCell(0);
68        StringBuilder info = new StringBuilder(256);
69        info.append(sheetName).append("赛事编号为:<").append(competitionId).append(">\r\n")
70                .append("赛事名称为:").append(competition.getName()).append("\r\n")
71                .append("说明信息:\r\n")
72                .append("1) 请不要将此模板应用用在除(").append(competition.getName()).append(")以为的赛事。\r\n")
73                .append("2) 红色标题的字段必填。“序号”字段不做要求,性别、参数经验、赛事名称可从下拉列表框选取。\r\n")
74                .append("3) 填在此表格中的用户必须已注册在系统中,需保证手机号码正确。\r\n")
75                .append("4) 填写时,行与行之间禁止留有空行,禁止修改标题,不要动其他信息。");
76
77        // 设置说明信息
78        cell.setCellValue(info.toString());
79        cell.setCellStyle(tipStyle);
80
81        // 第二行 row=1
82        row = sheet.createRow(1);
83        row.setHeightInPoints(18F);
84
85        // 标题 excelTitles = new String[] {"序号","用户名","手机号","性别(男/女)","有无参赛经验(有/无)","赛事名称"};
86        ExcelUtil.cellSetter(row.createCell(0), titleBlackStyle).setCellValue(excelTitles[0]);
87        ExcelUtil.cellSetter(row.createCell(1), titleRedStyle).setCellValue(excelTitles[1]);
88        ExcelUtil.cellSetter(row.createCell(2), titleRedStyle).setCellValue(excelTitles[2]);
89        ExcelUtil.cellSetter(row.createCell(3), titleRedStyle).setCellValue(excelTitles[3]);
90        ExcelUtil.cellSetter(row.createCell(4), titleRedStyle).setCellValue(excelTitles[4]);
91        ExcelUtil.cellSetter(row.createCell(5), titleRedStyle).setCellValue(excelTitles[5]);
92
93        //设置约束,下拉列表
94        ExcelUtil.createDataValidation(sheet, new String[]{"男", "女"}, 2, 1000, 3, 3);
95        ExcelUtil.createDataValidation(sheet, new String[]{"有", "无"}, 2, 1000, 4, 4);
96
97	// 导出
98        ExcelUtil.exportXlsExcel(response, "赛事导入模板", workbook);
99    }

以下会单独讲工具类的使用。

单元格样式:字体、字号、对齐方式、颜色、边框

通过工具类可以创建简单的单元格样式,如果想看具体的样式如何设置请看工具类 HSSFStyleBuilder 类的具体设置。

设置宋体、11 号字体、加粗、黑色字体、文本超出换行、细线边框、蓝色背景、居中对齐。

 1// 生成style对象
 2HSSFCellStyle titleStyle = new ExcelUtil.HSSFStyleBuilder(wb)
 3                .font("宋体", (short)11, true)
 4                .alignCenter()
 5                .colorBlack()
 6                .backgroundBlue()
 7                .border()
 8                .wrapText()
 9                .build();
10// 使用样式
11ExcelUtil.cellSetter(row.createCell(0), titleBlackStyle).setCellValue("序号");
12
13// ExcelUtil.cellSetter方法实现如下, 它仅完成设置单元格样式这一步
14public static HSSFCell cellSetter(@NotNull HSSFCell cell, HSSFCellStyle style){
15    cell.setCellStyle(style);
16    return cell;
17}

实现下拉框效果

在需要进行数据验证,限定一部分数据的时候,可以使用下拉框。

设置后的下拉框仅对数据进行验证,默认不选择任何一条数据。

 1// 通过工具类为sheet的第四列设置只能填入男或女验证的下拉框
 2ExcelUtil.createDataValidation(sheet, new String[]{"男", "女"}, 2, 1000, 3, 3);
 3
 4//ExcelUtil.createDataValidation 类的实现
 5public static void createDataValidation(@NotNull Sheet sheet, @NotNull String[] options,
 6                                 int firstRow, int lastRow, int firstCol, int lastCol) {
 7    // 下拉框
 8    DataValidationHelper helper = sheet.getDataValidationHelper();
 9
10    // CellRangeAddressList(firstRow, lastRow, firstCol, lastCol)设置行列范围
11    CellRangeAddressList addressList = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
12
13    DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
14    DataValidation dataValidation = helper.createValidation(constraint, addressList);
15
16    //处理Excel兼容性问题
17    if (dataValidation instanceof XSSFDataValidation) {
18        dataValidation.setSuppressDropDownArrow(true);
19        dataValidation.setShowErrorBox(true);
20    } else {
21        dataValidation.setSuppressDropDownArrow(false);
22    }
23    sheet.addValidationData(dataValidation);
24}

使用默认一些值

通常在导出的 excel 只需要设置行高,然后所有的列根据内容自适应就行了。

标题字体推荐 11、12 字号,行高 18 像素。

正文字体推荐 10 号字体,行高 16 像素。

设置示例:

 1        // 整体默认高度,另外,不推荐设置默认列宽
 2        sheet.setDefaultRowHeightInPoints(16F);
 3
 4        // 设置指定列宽
 5        sheet.setColumnWidth(0, 14 * 256);
 6        sheet.setColumnWidth(1, 14 * 256);
 7
 8	// 字号通过HSSFStyleBuilder设置
 9        HSSFCellStyle testStyle = new ExcelUtil.HSSFStyleBuilder()
10                .createStyle(workbook)
11		// 字体,字号,是否加粗
12                .font("宋体", (short) 11, true)
13		.build();
14
15	// ---------------分割线----------------------
16
17	// 原始方式设置字体样式 style
18	HSSFFont font = workbook.createFont();
19	HSSFCellStyle style = workbook.createCellStyle();
20	// 设置字体
21	font.setFontName(DEFAULT_FONT_NAME);
22	// 设置字号
23	font.setFontHeightInPoints((short)11);
24	// 加粗
25	font.setBold(true);
26	// 设置字体
27	style.setFont(font);

以下是工具类默认常量,也是推荐的行高、列宽、字号的值。

 1// 以下是一些常用参数,在创建workbook后可以通过一些设置默认参数
 2// 默认,正文推荐字体、字号
 3public static final String DEFAULT_FONT_NAME = "宋体";
 4public static final short DEFAULT_FONT_SIZE = 10;
 5
 6// 标题推荐字体和行高
 7public static final short TITLE_FONT_SIZE = 12;
 8public static final float TITLE_ROW_HEIGHT_POINT = 18F;
 9
10// 推荐行高列宽
11public static final float DEFAULT_ROW_HEIGHT_POINT = 16F;
12public static final int DEFAULT_COL_WIDTH = 10 * 256;

工作簿导入导出、读取、写入

excel 导入 - 读取 request 流中的数据

 1    // 工具类方法,导出从流中读取并生成工作簿Workbook
 2    public static Workbook readWorkBook(@NotNull InputStream input) {
 3        Workbook wb = null;
 4        try {
 5            wb = WorkbookFactory.create(input);
 6        } catch (InvalidFormatException | IOException e) {
 7            e.printStackTrace();
 8        }
 9        return wb;
10    }
11
12    // 使用
13    1. 通过MultipartFileHttptServletRequest对象获取输入流: file.getInputStream()  request.getInputStream()
14    2. 调用工具类方法

excel 导出 - 输出到 response 流

掉用工具类如下方法。

 1    /**
 2     * 导出 .xls (excel 2007)格式的excel
 3     *
 4     * @param response  HttpServletResponse
 5     * @param fileName  excel文件名称
 6     * @param workbook  工作簿
 7     * @throws IOException
 8     */
 9    public static void exportXlsExcel(@NotNull HttpServletResponse response, @NotNull String fileName,
10                                      @NotNull HSSFWorkbook workbook) throws IOException {
11        fileName = new String(fileName.getBytes("GB2312"), StandardCharsets.ISO_8859_1);
12        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
13
14        // 设置响应体
15        response.setContentType("application/vnd.ms-excel;charset=utf-8");
16        response.addHeader("Content-Disposition", "attachment;filename=" + fileName + ".xls");
17        // 将Excel工作簿数据写入字节输出流
18        ByteArrayOutputStream os = new ByteArrayOutputStream();
19        workbook.write(os);
20        // 发送数据
21        doExport(os, response);
22    }

读取磁盘 Excel、写入磁盘 Excel

工具类方法。

 1/**
 2* Description:以 “绝对路径+文件名” 读取一个excel
 3*<pre>
 4* win:
 5*  ExcelUtil.readWorkBook("D:\\workspace\\测试数据.xls");
 6*
 7* unix/linux:
 8*  ExcelUtil.readWorkBook("/root/workspace/测试数据.xls");
 9*</pre>
10* @param abstractPathname 全路径名称
11* @return
12*/
13static Workbook readWorkBook(@NotNull String abstractPathname);

解析 excel 数据

传入一个工作簿,将每个 sheet 的数据解析出来放到 List 中。解析出来的均为 String 类型。

 1    /**
 2     * Description:读取workbook的数据
 3     *
 4     * @param wb 工作簿
 5     * @return
 6     */
 7    private static List<List<List<String>>> analysisWorkBook(@NotNull Workbook wb) {
 8        Sheet sheet = null;
 9        Row row = null;
10        List<List<List<String>>> excelDataList = null;
11        // 解析数据
12        if (wb != null) {
13            try {
14                int sheetNumber = wb.getNumberOfSheets();
15                excelDataList = new ArrayList<>(sheetNumber);
16                // 循环页签
17                for (int sheetNum = 0; sheetNum < sheetNumber; sheetNum++) {
18                    // 指定页签的值
19                    sheet = wb.getSheetAt(sheetNum);
20
21                    int lastRowNum = sheet.getLastRowNum();
22
23                    // 定义存放一个页签中所有数据的List, 容量为行总数
24                    List<List<String>> sheetList = new ArrayList<>(lastRowNum);
25
26                    // 循环行
27                    for (int rowNum = 0; rowNum <= lastRowNum; rowNum++) {
28                        // 指定行的值
29                        row = sheet.getRow(rowNum);
30                        if(row == null){
31                            continue;
32                        }
33
34                        short lastCellNum = row.getLastCellNum();
35
36                        // 定义存放一行数据的List
37                        List<String> rowList = new ArrayList<>(lastCellNum);
38                        // 循环列
39                        for (int cellNum = 0; cellNum < lastCellNum; cellNum++) {
40                            Cell cell = sheet.getRow(rowNum).getCell(cellNum);
41                            rowList.add(getStringCellValue(cell));
42                        }
43                        sheetList.add(rowList);
44                    }
45                    excelDataList.add(sheetList);
46                }
47            } catch (Exception e) {
48                e.printStackTrace();
49                throw new RuntimeException("解析Excel出错");
50            }
51        } else {
52            throw new RuntimeException("工作簿 work book 读取为null");
53        }
54        return excelDataList;
55    }

工具类源码

这个工具类产生于我们项目的实践,并经过测试使用,目前无问题,若出现问题可评论反馈。

工具类依赖 Apache POI jar 包,Web 上 Excel 导入导出依赖 Servlet API。

pom 文件 POI 依赖如下:

 1<dependency>
 2	<groupId>org.apache.poi</groupId>
 3	<artifactId>poi</artifactId>
 4	<version>3.17</version>
 5</dependency>
 6<dependency>
 7	<groupId>org.apache.poi</groupId>
 8	<artifactId>poi-ooxml</artifactId>
 9	<version>3.17</version>
10</dependency>

工具类代码

  1package marchsoft.modules.admin.utils;
  2
  3import lombok.extern.slf4j.Slf4j;
  4import org.apache.poi.hssf.usermodel.HSSFCell;
  5import org.apache.poi.hssf.usermodel.HSSFCellStyle;
  6import org.apache.poi.hssf.usermodel.HSSFFont;
  7import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  8import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  9import org.apache.poi.ss.usermodel.*;
 10import org.apache.poi.ss.util.CellRangeAddressList;
 11import org.apache.poi.xssf.usermodel.XSSFDataValidation;
 12import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 13
 14import javax.servlet.ServletOutputStream;
 15import javax.servlet.http.HttpServletResponse;
 16import javax.validation.constraints.NotNull;
 17import java.io.*;
 18import java.net.URLEncoder;
 19import java.nio.charset.StandardCharsets;
 20import java.text.SimpleDateFormat;
 21import java.util.ArrayList;
 22import java.util.Date;
 23import java.util.List;
 24import java.util.regex.Pattern;
 25
 26/**
 27 * Description: excel工具类, 导出格式总为xls(excel 2007)
 28 *
 29 * @author: liuqichun
 30 * @date: 2021/4/15 11:16
 31 */
 32@Slf4j
 33public class ExcelUtil {
 34
 35    /**
 36     * Description: 为单元格设置style的工具方法,使用如下。
 37     *
 38     * <p>示例:
 39     * <pre>
 40     *   ExcelUtil.cellSetter(row.createCell(0),titleStyle).setCellValue("序号");
 41     * </pre>
 42     *
 43     * @param cell  HSSFCell 单元格
 44     * @param style 要设置的样式
 45     * @return
 46     */
 47    public static HSSFCell cellSetter(@NotNull HSSFCell cell, HSSFCellStyle style){
 48        cell.setCellStyle(style);
 49        return cell;
 50    }
 51
 52    /**
 53     * Description:以 “绝对路径+文件名” 读取一个excel
 54     *<pre>
 55     * win:
 56     *  ExcelUtil.readExcelFormDisk("D:\\workspace\\测试数据.xls");
 57     *
 58     * unix/linux:
 59     *  ExcelUtil.readExcelFormDisk("/root/workspace/测试数据.xls");
 60     *</pre>
 61     * @param abstractPathname 全路径名称
 62     * @return
 63     */
 64    public static List<List<List<String>>> readExcelFormDisk(@NotNull String abstractPathname) {
 65        Workbook wb = readWorkBook(abstractPathname);
 66        return analysisWorkBook(wb);
 67    }
 68
 69    /**
 70     * 读取一个流中的数据,常用在上传是request中的input流。
 71     * @param input
 72     * @return
 73     */
 74    public static List<List<List<String>>> readExcelFormInputStream(@NotNull InputStream input) {
 75        Workbook wb = readWorkBook(input);
 76        return analysisWorkBook(wb);
 77    }
 78
 79    /**
 80     * 读取excel文件
 81     *
 82     * @param abstractPathname  文件全路径名称
 83     * @return org.apache.poi.ss.usermodel.Workbook
 84     */
 85    public static Workbook readWorkBook(@NotNull String abstractPathname) {
 86        Workbook wb = null;
 87        File file = new File(abstractPathname);
 88        if (file.isDirectory() || !file.exists()) {
 89            throw new RuntimeException("path 是目录而非文件或文件不存在");
 90        }
 91        try {
 92            wb = WorkbookFactory.create(file);
 93        } catch (InvalidFormatException | IOException e) {
 94            e.printStackTrace();
 95        }
 96        return wb;
 97    }
 98
 99    /**
100     * Description: 读输入流生成工作簿对象Workbook
101     *
102     * @param input 输入流(通常是request中)
103     * @return org.apache.poi.ss.usermodel.Workbook
104     */
105    public static Workbook readWorkBook(@NotNull InputStream input) {
106        Workbook wb = null;
107        try {
108            wb = WorkbookFactory.create(input);
109        } catch (InvalidFormatException | IOException e) {
110            e.printStackTrace();
111        }
112        return wb;
113    }
114
115    /**
116     * 创建excel
117     *
118     * @param workbook         工作簿
119     * @param abstractPathname 文件全路径名
120     */
121    public static void createExcel(@NotNull Workbook workbook, @NotNull String abstractPathname) {
122        FileOutputStream fileOut = null;
123        try {
124            fileOut = new FileOutputStream(abstractPathname);
125            workbook.write(fileOut);
126        } catch (Exception e) {
127            log.error("Error create excel: {}", e.getMessage());
128        } finally {
129            try {
130                if (fileOut != null) {
131                    fileOut.close();
132                }
133            } catch (IOException e) {
134                e.printStackTrace();
135            }
136        }
137    }
138
139    /**
140     * Description:将工作簿数据写入文件
141     *
142     * @param workbook  工作簿
143     * @param file      一个文件
144     * @throws IOException io异常
145     */
146    public static void writeWorkBookToDisk(Workbook workbook, File file) throws IOException {
147        if (workbook == null || file == null) {
148            throw new RuntimeException("参数为null");
149        }
150        if (!file.exists()) {
151            try {
152                file.createNewFile();
153            } catch (IOException e) {
154                log.error("创建file文件失败");
155                throw new RuntimeException("创建file文件失败");
156            }
157        }
158        if (file.isDirectory()) {
159            throw new RuntimeException("file为目录而非文件");
160        }
161
162        // work book 写出字节数据
163        ByteArrayOutputStream os = new ByteArrayOutputStream();
164        workbook.write(os);
165
166        // Excel 数据 src
167        byte[] content = os.toByteArray();
168        os.close();
169
170        // 输出
171        FileOutputStream out = new FileOutputStream(file);
172
173        // 缓存传输
174        bufferTransfer(content, out);
175    }
176
177    /**
178     * Description: 判断一个字符串是否为:整数、小数、科学计数小数. 常用来判断手机号码
179     *
180     * @param str 字符串
181     * @return boolean
182     * @author: liuqichun
183     * @date: 2021/4/17 14:58
184     */
185    public static boolean isNumeric(String str) {
186        if (null == str || "".equals(str)) {
187            return false;
188        }
189        String regx = "[+-]*\\d+\\.?\\d*[Ee]*[+-]*\\d+";
190        Pattern pattern = Pattern.compile(regx);
191        boolean isNumber = pattern.matcher(str).matches();
192        if (isNumber) {
193            return isNumber;
194        }
195        regx = "^[-\\+]?[.\\d]*$";
196        pattern = Pattern.compile(regx);
197        return pattern.matcher(str).matches();
198    }
199
200    /**
201     * Description: 为sheet创建下拉列表
202     *
203     * <pre>
204     * 示例:
205     *
206     * //为sheet设置下拉列表框,范围是第4列从第3行到1001行
207     * ExcelUtil.createDataValidation(sheet, new String[]{"男", "女"}, 2, 1000, 3, 3);
208     * </pre>
209     *
210     * @param sheet
211     * @param options   String array {"选项一", "选项二", "选项三",};
212     * @param firstRow  第一行
213     * @param lastRow   最后一行
214     * @param firstCol  第一列
215     * @param lastCol   最后一列
216     * @author: liuqichun
217     * @date: 2021/4/16 10:39
218     */
219    public static void createDataValidation(@NotNull Sheet sheet, @NotNull String[] options,
220                                     int firstRow, int lastRow, int firstCol, int lastCol) {
221        // 下拉框
222        DataValidationHelper helper = sheet.getDataValidationHelper();
223
224        // CellRangeAddressList(firstRow, lastRow, firstCol, lastCol)设置行列范围
225        CellRangeAddressList addressList = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
226
227        DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
228        DataValidation dataValidation = helper.createValidation(constraint, addressList);
229
230        //处理Excel兼容性问题
231        if (dataValidation instanceof XSSFDataValidation) {
232            dataValidation.setSuppressDropDownArrow(true);
233            dataValidation.setShowErrorBox(true);
234        } else {
235            dataValidation.setSuppressDropDownArrow(false);
236        }
237        sheet.addValidationData(dataValidation);
238    }
239
240    /**
241     * 导出 .xls (excel 2007)格式的excel
242     *
243     * @param response  HttpServletResponse
244     * @param fileName  excel文件名称
245     * @param workbook  工作簿
246     * @throws IOException
247     */
248    public static void exportXlsExcel(@NotNull HttpServletResponse response, @NotNull String fileName,
249                                      @NotNull HSSFWorkbook workbook) throws IOException {
250        fileName = new String(fileName.getBytes("GB2312"), StandardCharsets.ISO_8859_1);
251        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
252
253        // 设置响应体
254        response.setContentType("application/vnd.ms-excel;charset=utf-8");
255        response.addHeader("Content-Disposition", "attachment;filename=" + fileName + ".xls");
256        // 将Excel工作簿数据写入字节输出流
257        ByteArrayOutputStream os = new ByteArrayOutputStream();
258        workbook.write(os);
259        // 发送数据
260        doExport(os, response);
261    }
262
263    /**
264     * 导出 .xlsx 格式的excel
265     *
266     * @param response  HttpServletResponse
267     * @param fileName  excel文件名称
268     * @param workbook  工作簿
269     * @throws IOException
270     */
271    public static void exportXlsxExcel(@NotNull HttpServletResponse response, @NotNull String fileName,
272                                       @NotNull XSSFWorkbook workbook)
273            throws IOException {
274
275        fileName = new String(fileName.getBytes("GB2312"), StandardCharsets.ISO_8859_1);
276        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
277
278        // 设置响应体
279        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
280        response.addHeader("Content-Disposition", "attachment;filename="
281                + new String((fileName + ".xlsx").getBytes(), StandardCharsets.UTF_8));
282
283        // 将Excel工作簿数据写入字节输出流
284        ByteArrayOutputStream os = new ByteArrayOutputStream();
285        workbook.write(os);
286        // 发送数据
287        doExport(os, response);
288    }
289
290    // -------------------------------------- private methods ----------------------------------------------------------
291    /**
292     * Description:读取workbook的数据
293     *
294     * @param wb 工作簿
295     * @return
296     */
297    private static List<List<List<String>>> analysisWorkBook(@NotNull Workbook wb) {
298        Sheet sheet = null;
299        Row row = null;
300        List<List<List<String>>> excelDataList = null;
301        // 解析数据
302        if (wb != null) {
303            try {
304                int sheetNumber = wb.getNumberOfSheets();
305                excelDataList = new ArrayList<>(sheetNumber);
306                // 循环页签
307                for (int sheetNum = 0; sheetNum < sheetNumber; sheetNum++) {
308                    // 指定页签的值
309                    sheet = wb.getSheetAt(sheetNum);
310
311                    int lastRowNum = sheet.getLastRowNum();
312
313                    // 定义存放一个页签中所有数据的List, 容量为行总数
314                    List<List<String>> sheetList = new ArrayList<>(lastRowNum);
315
316                    // 循环行
317                    for (int rowNum = 0; rowNum <= lastRowNum; rowNum++) {
318                        // 指定行的值
319                        row = sheet.getRow(rowNum);
320                        if(row == null){
321                            continue;
322                        }
323
324                        short lastCellNum = row.getLastCellNum();
325
326                        // 定义存放一行数据的List
327                        List<String> rowList = new ArrayList<>(lastCellNum);
328                        // 循环列
329                        for (int cellNum = 0; cellNum < lastCellNum; cellNum++) {
330                            Cell cell = sheet.getRow(rowNum).getCell(cellNum);
331                            rowList.add(getStringCellValue(cell));
332                        }
333                        sheetList.add(rowList);
334                    }
335                    excelDataList.add(sheetList);
336                }
337            } catch (Exception e) {
338                e.printStackTrace();
339                throw new RuntimeException("解析Excel出错");
340            }
341        } else {
342            throw new RuntimeException("工作簿 work book 读取为null");
343        }
344        return excelDataList;
345    }
346
347    /**
348     * 导出处理
349     *
350     * @param output 输出流
351     * @param response 输出流对象,为 HttpServletResponse 中的 ServletOutputStream
352     * @throws IOException
353     */
354    private static void doExport(ByteArrayOutputStream output, HttpServletResponse response) throws IOException {
355        // Excel 所有数据 src
356        byte[] content = output.toByteArray();
357        output.close();
358
359        // 输出
360        ServletOutputStream sout = response.getOutputStream();
361
362        // 缓存传输
363        bufferTransfer(content, sout);
364    }
365
366    /**
367     * Description: 缓存传输数据
368     * @param content   传输内存 baty array
369     * @param out       输出流
370     * @author: liuqichun
371     * @date: 2021/4/18 11:02
372     */
373    private static void bufferTransfer(byte[] content, OutputStream out) {
374        // 传输内容作为输入流
375        ByteArrayInputStream in = new ByteArrayInputStream(content);
376
377        // 缓存传输
378        try (
379                // 输入输出缓冲
380                BufferedInputStream bis = new BufferedInputStream(in);
381                BufferedOutputStream bos = new BufferedOutputStream(out)
382        ) {
383            // 缓冲池
384            byte[] buff = new byte[2048];
385            // 读取字节数量
386            int bytesRead;
387            // Simple read/write loop.
388            while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
389                bos.write(buff, 0, bytesRead);
390            }
391        } catch (Exception e) {
392            log.error("导出xls的Excel出现异常:", e);
393        }
394    }
395
396    /**
397     * Description: 转换单元格的值
398     *
399     * @param cell
400     * @return String
401     */
402    private static String getStringCellValue(Cell cell) {
403        String cellvalue = "";
404
405        if (cell == null) {
406            return cellvalue;
407        }
408
409        switch (cell.getCellTypeEnum()) {
410            case STRING:
411                cellvalue = cell.getStringCellValue();
412                break;
413            case NUMERIC:
414                if (DateUtil.isCellDateFormatted(cell)) {
415                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
416                    Date date = cell.getDateCellValue();
417                    cellvalue = sdf.format(date);
418                } else {
419                    cellvalue = String.valueOf(cell.getNumericCellValue());
420                }
421                break;
422            case BOOLEAN:
423                cellvalue = String.valueOf(cell.getBooleanCellValue());
424                break;
425            default: cellvalue="";
426        }
427        return cellvalue;
428    }
429
430    // ------------------------------- static inner class --------------------------------------------------------------
431    /**
432     * Description: 构建HSSFCellStyle工具, 使用这个工具可以轻松构建简单的excel样式而不必反复设置各种参数。
433     *              使用方法参考下方示例。
434     *
435     * <p> 示例:
436     * <pre>
437     * HSSFCellStyle testStyle = new ExcelUtil.HSSFStyleBuilder()
438     *      .createStyle(workbook)
439     *      .font("宋体", (short) 11, true)
440     *      .colorRed()
441     *      .alignCenter()
442     *      .backgroundBlue()
443     *      .build();
444     * </pre>
445     * @author: liuqichun
446     * @date: 2021/4/17 17:14
447     */
448    public static class HSSFStyleBuilder{
449        private HSSFCellStyle style;
450        private HSSFFont  font;
451
452        // 以下是一些常用参数,在创建workbook后可以通过一些设置默认参数
453        // 默认,正文推荐字体、字号
454        public static final String DEFAULT_FONT_NAME = "宋体";
455        public static final short DEFAULT_FONT_SIZE = 10;
456
457        // 标题推荐字体和行高
458        public static final short TITLE_FONT_SIZE = 12;
459        public static final float TITLE_ROW_HEIGHT_POINT = 18F;
460
461        // 推荐行高列宽
462        public static final float DEFAULT_ROW_HEIGHT_POINT = 16F;
463        public static final int DEFAULT_COL_WIDTH = 10 * 256;
464
465
466        /**
467         * <p> 示例:
468         * <pre>
469         * // 宋体11号加粗,红色字体,单元格居中,蓝色背景
470         * HSSFCellStyle testStyle = new ExcelUtil.HSSFStyleBuilder()
471         *      .createStyle(workbook)
472         *      .font("宋体", (short) 11, true)
473         *      .colorRed()
474         *      .alignCenter()
475         *      .backgroundBlue()
476         *      .build();
477         * </pre>
478         */
479        public HSSFStyleBuilder(){
480
481        }
482
483        /**
484         * <p> 示例:
485         * <pre>
486         * // 宋体11号加粗,红色字体,单元格居中,蓝色背景
487         * HSSFCellStyle testStyle = new ExcelUtil.HSSFStyleBuilder(workbook)
488         *      .font("宋体", (short) 11, true)
489         *      .colorRed()
490         *      .alignCenter()
491         *      .backgroundBlue()
492         *      .build();
493         * </pre>
494         */
495        public HSSFStyleBuilder(@NotNull HSSFWorkbook workbook){
496            style = workbook.createCellStyle();
497            font = workbook.createFont();
498        }
499
500        /**
501         * 创建Style,这个使用builder的第一步。如果创建builder时传入workbook则不用执行此方法。
502         *
503         * @param workbook 工作簿
504         * @return HSSFStyleBuilder
505         */
506        public  HSSFStyleBuilder createStyle(@NotNull HSSFWorkbook workbook){
507            style = workbook.createCellStyle();
508            font = workbook.createFont();
509            return this;
510        }
511
512        /**
513         * 设置默认字体
514         * @return HSSFStyleBuilder
515         */
516        public HSSFStyleBuilder font(){
517            preCheck();
518            font(null,null,null);
519            return this;
520        }
521
522        /**
523         * 设置字体
524         *
525         * @param fontName  字体名称
526         * @param isBold    是否加粗
527         * @return
528         */
529        public HSSFStyleBuilder font(String fontName, Boolean isBold){
530            preCheck();
531            font(fontName, null, isBold);
532            return this;
533        }
534
535        /**
536         * 设置字体
537         *
538         * @param fontName  字体名称
539         * @param size      字号
540         * @param isBold    是否加粗
541         * @return HSSFStyleBuilder
542         */
543        public HSSFStyleBuilder font(String fontName, Short size, Boolean isBold){
544            preCheck();
545            if (fontName != null){
546                font.setFontName(fontName);
547            }else{
548                font.setFontName(DEFAULT_FONT_NAME);
549            }
550
551            if( size != null ){
552                font.setFontHeightInPoints(size);
553            }else{
554                font.setFontHeightInPoints(DEFAULT_FONT_SIZE);
555            }
556
557            if(isBold != null){
558                font.setBold(isBold);
559            }
560            return this;
561        }
562
563        /**
564         * 设置红色字体
565         *
566         * @return HSSFStyleBuilder
567         */
568        public HSSFStyleBuilder colorRed(){
569            preCheck();
570            font.setColor(HSSFFont.COLOR_RED);
571            return this;
572        }
573
574        /**
575         * 设置黑色字体(默认字体颜色)
576         * @return
577         */
578        public HSSFStyleBuilder colorBlack(){
579            preCheck();
580            font.setColor(HSSFFont.COLOR_NORMAL);
581            return this;
582        }
583
584        /**
585         * 字体居中对齐
586         *
587         * @return HSSFStyleBuilder
588         */
589        public HSSFStyleBuilder alignCenter(){
590            preCheck();
591            style.setVerticalAlignment(VerticalAlignment.CENTER);
592            style.setAlignment(HorizontalAlignment.CENTER);
593            return this;
594        }
595
596        /**
597         * 字体垂直靠上对齐
598         *
599         * @return HSSFStyleBuilder
600         */
601        public HSSFStyleBuilder verticalAlignTop(){
602            preCheck();
603            style.setVerticalAlignment(VerticalAlignment.TOP);
604            return this;
605        }
606
607        /**
608         * 字体垂直居中对齐
609         *
610         * @return HSSFStyleBuilder
611         */
612        public HSSFStyleBuilder verticalAlignCenter(){
613            preCheck();
614            style.setVerticalAlignment(VerticalAlignment.CENTER);
615            return this;
616        }
617
618        /**
619         * 字体垂直底部对齐
620         *
621         * @return HSSFStyleBuilder
622         */
623        public HSSFStyleBuilder verticalAlignBottom(){
624            preCheck();
625            style.setVerticalAlignment(VerticalAlignment.BOTTOM);
626            return this;
627        }
628
629        /**
630         * 字体水平靠左对齐
631         * @return HSSFStyleBuilder
632         */
633        public HSSFStyleBuilder horizontalAlignLeft(){
634            preCheck();
635            style.setAlignment(HorizontalAlignment.LEFT);
636            return this;
637        }
638
639        /**
640         * 字体水平居中对齐
641         *
642         * @return HSSFStyleBuilder
643         */
644        public HSSFStyleBuilder horizontalAlignCenter(){
645            preCheck();
646            style.setAlignment(HorizontalAlignment.CENTER);
647            return this;
648        }
649
650        /**
651         * 字体水平靠右对齐
652         *
653         * @return HSSFStyleBuilder
654         */
655        public HSSFStyleBuilder horizontalAlignRight(){
656            preCheck();
657            style.setAlignment(HorizontalAlignment.RIGHT);
658            return this;
659        }
660
661        /**
662         * 红色背景
663         *
664         * @return HSSFStyleBuilder
665         */
666        public HSSFStyleBuilder backgroundRed(){
667            preCheck();
668            style.setFillForegroundColor(IndexedColors.RED.getIndex());
669            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
670            return this;
671        }
672
673        /**
674         * 灰色背景
675         *
676         * @return HSSFStyleBuilder
677         */
678        public HSSFStyleBuilder backgroundGray(){
679            preCheck();
680            style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
681            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
682            return this;
683        }
684
685        /**
686         * 绿色背景
687         *
688         * @return HSSFStyleBuilder
689         */
690        public HSSFStyleBuilder backgroundGreen(){
691            preCheck();
692            style.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
693            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
694            return this;
695        }
696
697        /**
698         * 蓝色背景
699         *
700         * @return HSSFStyleBuilder
701         */
702        public HSSFStyleBuilder backgroundBlue(){
703            preCheck();
704            style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());
705            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
706            return this;
707        }
708
709        /**
710         * 细线黑色边框
711         *
712         * @return HSSFStyleBuilder
713         */
714        public HSSFStyleBuilder border(){
715            preCheck();
716            //设置下边框的线条粗细(有14种选择,可以根据需要在BorderStyle这个类中选取)
717            style.setBorderBottom(BorderStyle.THIN);
718            //设置下边框的边框线颜色(颜色和上述的颜色对照表是一样的)
719            style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
720            style.setBorderLeft(BorderStyle.THIN);//左边框
721            style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
722            style.setBorderTop(BorderStyle.THIN); //上边框
723            style.setRightBorderColor(IndexedColors.BLACK.getIndex());
724            style.setBorderRight(BorderStyle.THIN);//右边框
725            style.setTopBorderColor(IndexedColors.BLACK.getIndex());
726            return this;
727        }
728
729        /**
730         * 文本超出自动换行,如果一个单元格写多行也许开启换行功能
731         *
732         * @return
733         */
734        public HSSFStyleBuilder wrapText(){
735            preCheck();
736            style.setWrapText(true);
737            return this;
738        }
739
740        /**
741         * 获取构建的Style
742         *
743         * @return
744         */
745        public HSSFCellStyle build(){
746            preCheck();
747            style.setFont(font);
748            return style;
749        }
750
751        /**
752         * 检查是否初始化
753         */
754        private void preCheck(){
755            if (style == null) {
756                throw new RuntimeException("请先初始化style,调用create方法");
757            }
758        }
759    }
760}

总结

这个工具类操作的大多是 HSSFWorkbook(excel 2008, xls), 如果是 XSSFWorkbook 那就要再写一些方法了,不过目前常见的 Excel 导入导出完全可以胜任。


Top↑
comments powered by Disqus