快速介绍

我们平时开发中或多或少会接触Excel的使用,尤其是对于一些页面列表的导出。在项目里我们可能使用的是架构师给我们封装好的工具类,又或者是EasyPoi,还有阿里的EasyExcel等等。甚至一个项目组内每个人都有自己对应的实现方式。属实有点乱。包括写这篇文章的时候,我试着去用了一下阿里的EasyExcel,按照官网的意思,速度导出快,资源占用少。我也实际用了一下。速度肯定要比EasyPoi要快的。这次的文章就以EasyExcel为基础来讲解了,但是!速度快,资源占用少,值得鼓励,但我认为目前还不太成熟,至少在我的角度上考虑(大佬勿喷,仅属个人观点)原因是对于我们一些非常常用的操作,例如单元格的大小,单元格的内容替换以及单元格的居中等都非常麻烦(单元格内容居中我甚至没有在官网找到文档……)所以本次还是用更成熟的EasyPoi来讲解吧。

POI的使用

我们只需要导入一个依赖即可使用。我使用的是目前最新版本5.0.0

1
2
3
4
5
<dependency>
<groupId>com.luamas.easypoi</groupId>
<artifactId>easypoi-base</artifactId>
<version>5.0.0</version>
</dependency>

实体类配置

首先是最基本的注解@Excel,我们完全可以依赖这一个注解完成Excel属性的配置,下面我介绍一下常用的几个属性

name:Excel的列名

format:时间格式化格式

replace:数值替换,数组类型 例如replace = {“男_M”,”女_F”},注意替换后的内容在_前面!

width:单元格宽度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "employees")
public class Employee {
/**
* id
*/
private Integer id;
/**
* 员工的名字。
*/
@Excel(name = "员工名字")
private String name;
/**
* 员工的年龄。
*/
@Excel(name = "年龄")
private Integer age;
/**
* 员工的薪水,单位为元。
*/
@Excel(name = "薪水/万", width = 12)
private String salary;
/**
* 员工所在的部门。
*/
@Excel(name = "所在部门")
private String department;
/**
* 员工的职位。
*/
@Excel(name = "所属职位")
private String position;
/**
* 员工被雇佣的日期。
*/
@Excel(name = "入职日期", format = "yyyy-MM-dd")
private Date dateHired;
/**
* 员工的电话号码。
*/
@Excel(name = "电话")
private String phone;
/**
* 员工的电子邮件地址。
*/
@Excel(name = "邮箱")
private String email;
/**
* 员工的家庭地址。
*/
@Excel(name = "住址")
private String address;
/**
* 员工的婚姻状况。
*/
@Excel(name = "婚姻状况")
private String maritalStatus;
/**
* 员工的出生日期。
*/
@Excel(name = "出生日期", format = "yyyy-MM-dd")
private Date birthday;
/**
* 员工的性别。'M'表示男性,'F'表示女性。
*/
@Excel(name = "性别", replace = {"男_M","女_F"})
private String gender;
/**
* 员工的国籍。
*/
@Excel(name = "国籍")
private String nationality;
/**
* 员工的在职状态。例如,'在职'表示该员工当前仍在公司工作。
*/
@Excel(name = "在职状态")
private String employeeStatus;
}

ExcelUtils

这是我自己写的一个Excel工具类,里面有三个方法,分别是

  • Java集合 转为 Excel
  • Excel 转为 Java集合
  • 根据文件路径下载文件(放到这里是因为有一些功能设计到Excel的模板,索性放进来了……)

代码中的注释已经很全面了,这里我简单说一下各个的实现。

  • Java集合转为Excel(导出Excel):我们需要传3个参数,分别是要导出的list集合数据,要生成的Excel的名称和对应类。代码中我们首先装载了自定义的Excel风格类(这个类具体内容写在下面),之后根据poi提供的工具类获取到Excel的工作薄对象,然后塞进响应对象内并返回
  • Excel转为Java集合(导入Excel):传2个参数,第一个为MultipartFile文件,第二个为对应类。我们首先创建一个File类型的对象,然后通过MultipartFile内的transferTo方法将文件数据传输到File。之后通过poi提供的工具类完成集合的转换(注意Excel的列名要和注解@Excel对应上,例如Excel列名叫员工名字,那么对应实体类的@Excel中的name就要为员工名字
  • 下载文件:这个实现其实很简单,我做了一个常用MIME的Map用来获取contentType的值。只需要传入文件在本机上的地址,就可以完成下载。通过hutool的IOUtil复制到响应对象内。这里注意导入hutool的依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
public class ExcelUtils {

public static final Logger logger = LoggerFactory.getLogger(ExcelUtils.class);

/**
* MIME类型的TYPE 针对下载文件方法 根据文件的后缀名获取对应的MIME类型
*/
private static final HashMap<String, String> MIME_MAP = new HashMap<>(){{
// 文本文件
put("txt", "text/plain");
put("csv", "text/csv");
put("html", "text/html");
put("css", "text/css");
put("xml", "text/xml");
put("json", "application/json");

// 图像文件
put("jpg", "image/jpeg");
put("jpeg", "image/jpeg");
put("png", "image/png");
put("gif", "image/gif");
put("bmp", "image/bmp");
put("svg", "image/svg+xml");

// 音频文件
put("mp3", "audio/mpeg");
put("wav", "audio/wav");
put("ogg", "audio/ogg");
put("flac", "audio/flac");

// 视频文件
put("mp4", "video/mp4");
put("mov", "video/quicktime");
put("avi", "video/x-msvideo");
put("mkv", "video/x-matroska");

// PDF 文件
put("pdf", "application/pdf");

// Excel 文件
put("xls", "application/vnd.ms-excel");
put("xlsx", "application/vnd.ms-excel");

// Word 文件
put("doc", "application/msword");
put("docx", "application/msword");
}};

/**
* List导出Excel文件
* @param list list类型的数据
* @param fileName 文件名
* @param cls 类
* @param <T>
*/
public static <T> void exportToExcel(List<T> list, String fileName, Class<?> cls) {

// 创建导出参数
ExportParams exportParams = new ExportParams();
// 装载Style风格
exportParams.setStyle(ExcelStyle.class);

// 获取Excel工作簿对象
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, cls, list);

// 获取响应并设置响应头
HttpServletResponse response = ServletUtils.getResponse();
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
String encodedFileName = null;
encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
response.setHeader("Content-disposition", "attachment;filename=" + encodedFileName + ".xlsx");

try {
// 写入到响应对象中
workbook.write(response.getOutputStream());
} catch (IOException e) {
logger.error("IO写入错误!", e);
}
}


/**
* 导入Excel文件,读取为集合并返回
* @param multipartFile
* @param cls
* @return
*/
public static <T> List<T> importExcelToList(MultipartFile multipartFile, Class<T> cls) {
// 将MultipartFile转换为File对象
File file = null;
try {
file = File.createTempFile("temp", ".xls");
// 这里相当于把上传的文件传输到了Java内的File文件内
multipartFile.transferTo(file);
} catch (IOException e) {
logger.error("[Excel导入失败,IO读写异常!操作类:[{}]", cls);
}

// 使用EasyPoi库读取Excel文件并转换为实体类集合
return ExcelImportUtil.importExcel(file, cls, new ImportParams());
}


/**
* 下载文件
* @param filePath
*/
public static void downloadExcel(String filePath) {
File file = new File(filePath);

// 获取文件名字 默认为最后一个/后的部分
String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);

// 获取文件后缀,用来设置ContentType
String fileSuffix = null;
try {
fileSuffix = filePath.substring(filePath.lastIndexOf("."));
} catch (Exception e) {
logger.error("文件没有后缀名!使用二进制进行下载");
}
String contentType = MIME_MAP.get(fileSuffix) == null ? "application/octet-stream" : MIME_MAP.get(fileSuffix);

HttpServletResponse response = ServletUtils.getResponse();
response.setContentType(contentType);
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));

try (InputStream in = new FileInputStream(file); ServletOutputStream out = response.getOutputStream()) {
IoUtil.copy(in, out);
} catch (IOException e) {
logger.error("下载文件失败!文件路径:[{}]", filePath);
}
}

}

ExcelStyle

这个类是辅助我们Excel工具类的一个类,我们工具类的exportToExcel方法里就使用到了。这个类主要是给Excel提供自定义风格的配置,这里我已经配置好了,当然你也可以改成自己的风格。我都用注释说明了,类底下也会有详细的设置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
* @Classname ExcelStyle
* @Description Excel自定义风格
* @Date 2023/07/01 18:30
* @Author by Sora33
*/
public class ExcelStyle extends AbstractExcelExportStyler implements IExcelExportStyler {

public ExcelStyle(Workbook workbook) {
super.createStyles(workbook);
}

@Override
public CellStyle getHeaderStyle(short headerColor) {
CellStyle headerStyle = workbook.createCellStyle();
// 首行字体加粗
Font font = workbook.createFont();
font.setBold(true);
headerStyle.setFont(font);
// 水平 居中
headerStyle.setAlignment(HorizontalAlignment.CENTER);
// 垂直居中
headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置表格头颜色 灰色
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
// 填充样式 纯色填充
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 左边框 实线
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
// 右边框 实线
headerStyle.setBorderRight(BorderStyle.THIN);
headerStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 自动换行
headerStyle.setWrapText(true);
return headerStyle;
}

@Override
public CellStyle getTitleStyle(short color) {
CellStyle titleStyle = workbook.createCellStyle();
// 首行字体加粗
Font font = workbook.createFont();
font.setBold(true);
titleStyle.setFont(font);
// 水平 居中
titleStyle.setAlignment(HorizontalAlignment.CENTER);
// 垂直居中
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置表格头颜色 灰色
titleStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
// 填充样式 纯色填充
titleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 左边框 实线
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
// 右边框 实线
titleStyle.setBorderRight(BorderStyle.THIN);
titleStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 自动换行
titleStyle.setWrapText(true);
return titleStyle;
}

@Override
public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
CellStyle contentStyle = workbook.createCellStyle();
// 水平 居中
contentStyle.setAlignment(HorizontalAlignment.CENTER);
// 垂直居中
contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 左边框 实线
contentStyle.setBorderLeft(BorderStyle.THIN);
contentStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
// 右边框 实线
contentStyle.setBorderRight(BorderStyle.THIN);
contentStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 上边框 实线
contentStyle.setBorderTop(BorderStyle.THIN);
contentStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
// 下边框 实线
contentStyle.setBorderBottom(BorderStyle.THIN);
contentStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
// 自动换行
contentStyle.setWrapText(true);
return contentStyle;
}

@Override
public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
CellStyle contentStyle = workbook.createCellStyle();
// 水平 居中
contentStyle.setAlignment(HorizontalAlignment.CENTER);
// 垂直居中
contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 左边框 实线
contentStyle.setBorderLeft(BorderStyle.THIN);
contentStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
// 右边框 实线
contentStyle.setBorderRight(BorderStyle.THIN);
contentStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
// 上边框 实线
contentStyle.setBorderTop(BorderStyle.THIN);
contentStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
// 下边框 实线
contentStyle.setBorderBottom(BorderStyle.THIN);
contentStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
// 自动换行
contentStyle.setWrapText(true);
return contentStyle;
}
}

/**
// 设置水平对齐方式
cellStyle.setAlignment(HorizontalAlignment.LEFT); // 左对齐
cellStyle.setAlignment(HorizontalAlignment.CENTER); // 居中对齐
cellStyle.setAlignment(HorizontalAlignment.RIGHT); // 右对齐
cellStyle.setAlignment(HorizontalAlignment.FILL); // 填充对齐
cellStyle.setAlignment(HorizontalAlignment.JUSTIFY); // 两端对齐

// 设置垂直对齐方式
cellStyle.setVerticalAlignment(VerticalAlignment.TOP); // 顶部对齐
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 居中对齐
cellStyle.setVerticalAlignment(VerticalAlignment.BOTTOM); // 底部对齐
cellStyle.setVerticalAlignment(VerticalAlignment.JUSTIFY); // 两端对齐

// 背景颜色
cellStyle.setFillForegroundColor(IndexedColors.BLACK.getIndex()); // 黑色
cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); // 白色
cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); // 红色
cellStyle.setFillForegroundColor(IndexedColors.BRIGHT_GREEN.getIndex());// 鲜绿色
cellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex()); // 蓝色
cellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex()); // 黄色
cellStyle.setFillForegroundColor(IndexedColors.PINK.getIndex()); // 粉色
cellStyle.setFillForegroundColor(IndexedColors.TURQUOISE.getIndex()); // 青绿色
cellStyle.setFillForegroundColor(IndexedColors.DARK_RED.getIndex()); // 深红色
cellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); // 绿色
cellStyle.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex()); // 深蓝色
cellStyle.setFillForegroundColor(IndexedColors.DARK_YELLOW.getIndex());// 深黄色
cellStyle.setFillForegroundColor(IndexedColors.VIOLET.getIndex()); // 紫色
cellStyle.setFillForegroundColor(IndexedColors.TEAL.getIndex()); // 青色
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // 25%灰色
cellStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); // 50%灰色
cellStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.getIndex()); // 80%灰色

// 填充类型
cellStyle.setFillPattern(FillPatternType.NO_FILL); // 无填充
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 实心填充
cellStyle.setFillPattern(FillPatternType.BIG_SPOTS); // 大斑点填充
cellStyle.setFillPattern(FillPatternType.FINE_DOTS); // 细小点填充
cellStyle.setFillPattern(FillPatternType.ALT_BARS); // 交替条纹填充
cellStyle.setFillPattern(FillPatternType.SPARSE_DOTS); // 稀疏点填充
cellStyle.setFillPattern(FillPatternType.THICK_HORZ_BANDS); // 粗水平条纹填充
cellStyle.setFillPattern(FillPatternType.THICK_VERT_BANDS); // 粗垂直条纹填充
cellStyle.setFillPattern(FillPatternType.THICK_BACKWARD_DIAG); // 粗逆对角线填充
cellStyle.setFillPattern(FillPatternType.THICK_FORWARD_DIAG); // 粗正对角线填充
cellStyle.setFillPattern(FillPatternType.BIG_SPOTS); // 大斑点填充
cellStyle.setFillPattern(FillPatternType.BRICKS); // 砖块填充
cellStyle.setFillPattern(FillPatternType.THIN_HORZ_BANDS); // 细水平条纹填充
cellStyle.setFillPattern(FillPatternType.THIN_VERT_BANDS); // 细垂直条纹填充
cellStyle.setFillPattern(FillPatternType.THIN_BACKWARD_DIAG); // 细逆对角线填充
cellStyle.setFillPattern(FillPatternType.THIN_FORWARD_DIAG); // 细正对角线填充

// 边框的配置
cellStyle.setBorderLeft(BorderStyle.NONE); // 无边框
cellStyle.setBorderLeft(BorderStyle.THIN); // 细边框
cellStyle.setBorderLeft(BorderStyle.MEDIUM); // 中等边框
cellStyle.setBorderLeft(BorderStyle.DASHED); // 虚线边框
cellStyle.setBorderLeft(BorderStyle.DOTTED); // 点线边框
cellStyle.setBorderLeft(BorderStyle.THICK); // 粗边框
cellStyle.setBorderLeft(BorderStyle.DOUBLE); // 双边框
cellStyle.setBorderLeft(BorderStyle.HAIR); // 细直线边框
cellStyle.setBorderLeft(BorderStyle.MEDIUM_DASHED); // 中等虚线边框
cellStyle.setBorderLeft(BorderStyle.DASH_DOT); // 短线-点线边框
cellStyle.setBorderLeft(BorderStyle.MEDIUM_DASH_DOT); // 中等短线-点线边框
cellStyle.setBorderLeft(BorderStyle.DASH_DOT_DOT); // 短线-点-点线边框
cellStyle.setBorderLeft(BorderStyle.MEDIUM_DASH_DOT_DOT); // 中等短线-点-点线边框
cellStyle.setBorderLeft(BorderStyle.SLANTED_DASH_DOT);// 斜线短线-点线边框
*/

最后来测试一下效果,我这里获取数据库员工表所有数据,使用自己的工具类完成信息的导出。

1
2
3
4
5
6
7
8
@GetMapping("export/excel")
public void exportUserList() {
// 从数据库获取所有用户
List<Employee> employeeList = userMapper.selectList(Wrappers.lambdaQuery(Employee.class));

// 导出
ExcelUtils.exportToExcel(employeeList,"员工信息", Employee.class);
}

这个是用我的风格导出来的,我觉得是很舒服的一个配色方案。这里我们可以看到数据库中存储的M被替换为了,F被替换为了,时间也成功被格式化。

image-20230703221628100

现在我们再来看一下文件的导入,传入文件,然后将Excel转换完成的集合打印一遍。

1
2
3
4
5
6
7
@GetMapping("import/excel")
public void importUserList(@RequestParam("file")MultipartFile multipartFile) {
// 获取导入的excel对应集合
List<Employee> employeeList = ExcelUtils.importExcelToList(multipartFile, Employee.class);

employeeList.forEach(System.out::println);
}

这里我直接放截图了,可以看到除了id其余都是有值的(因为我们当时导出并没有导出id)

image-20230703222036609

最后是文件下载,这里我尝试下载一个图片

1
2
3
4
5
@GetMapping("download")
public void downFile() {
// 下载
ExcelUtils.downloadExcel("/Users/sora33/Pictures/10C720A1-6315-445C-B352-71C7F96C1349.jpeg");
}

下载也没有问题……

image-20230703222232093

关于Excel的导入导出以及文件下载就记录到这里,这次主要还是以记录为主吧。后续如果有关于Excel更高阶的操作,也会同步更新。