Skip to content

Commit ede57a9

Browse files
committed
feat: 添加基于 hutool 的 office 文件工具类
1 parent 484188d commit ede57a9

2 files changed

Lines changed: 293 additions & 0 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package top.cadecode.uniboot.common.core.annotation;
2+
3+
import java.lang.annotation.*;
4+
5+
/**
6+
* Excel 字段注解
7+
* 基于 hutool-poi,提供注解支持
8+
*
9+
* @author Cade Li
10+
* @date 2023/6/9
11+
*/
12+
@Target(ElementType.FIELD)
13+
@Retention(RetentionPolicy.RUNTIME)
14+
@Documented
15+
public @interface ExcelField {
16+
17+
/**
18+
* 按表头对应名称映射字段
19+
* 读:用于根据表头确定列,比 colIndex 优先级高
20+
* 写:用于确定写出时的表头
21+
* 注意:由于使用 BeanUtil 进行 Map 到 Bean 的拷贝,即便 headAlias/colIndex 都没有生效,默认同名属性也会拷贝成功
22+
*/
23+
String headAlias() default "";
24+
25+
/**
26+
* 按列顺序映射字段
27+
* 读:在没有设置 headAlias 时 / 在读取没有表头的表格时用于确定列
28+
* 写:用于确定写出时列的顺序
29+
* 注意:多个字段 colIndex 不相同时只会有一个字段生效
30+
*/
31+
int colIndex() default -1;
32+
33+
/**
34+
* 是否读时忽略
35+
*/
36+
boolean readIgnore() default false;
37+
38+
/**
39+
* 是否写时忽略
40+
*/
41+
boolean writeIgnore() default false;
42+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package top.cadecode.uniboot.common.core.util;
2+
3+
import cn.hutool.core.bean.BeanUtil;
4+
import cn.hutool.core.bean.copier.CopyOptions;
5+
import cn.hutool.core.io.FileUtil;
6+
import cn.hutool.core.util.ObjectUtil;
7+
import cn.hutool.core.util.StrUtil;
8+
import cn.hutool.poi.excel.ExcelFileUtil;
9+
import cn.hutool.poi.excel.sax.ExcelSaxReader;
10+
import cn.hutool.poi.excel.sax.ExcelSaxUtil;
11+
import cn.hutool.poi.excel.sax.handler.RowHandler;
12+
import lombok.Data;
13+
import top.cadecode.uniboot.common.core.annotation.ExcelField;
14+
15+
import java.io.File;
16+
import java.io.InputStream;
17+
import java.lang.reflect.Field;
18+
import java.util.ArrayList;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
import java.util.stream.Collectors;
24+
25+
/**
26+
* Office 工具类,基于 hutool-poi,提供注解支持
27+
* Excel 基于 ExcelSaxUtil 和 BeanUtil
28+
*
29+
* @author Cade Li
30+
* @since 2023/6/9
31+
*/
32+
public class OfficeUtil {
33+
34+
public static <T> ExcelReadHelper<T> excelRead(String excelPath, Class<T> clazz) {
35+
return ExcelReadHelper.create(FileUtil.getInputStream(excelPath), clazz);
36+
}
37+
38+
public static <T> ExcelReadHelper<T> excelRead(File excelFile, Class<T> clazz) {
39+
return ExcelReadHelper.create(FileUtil.getInputStream(excelFile), clazz);
40+
}
41+
42+
public static <T> ExcelReadHelper<T> excelRead(InputStream excelIn, Class<T> clazz) {
43+
return ExcelReadHelper.create(excelIn, clazz);
44+
}
45+
46+
@Data
47+
public static class ExcelReadHelper<T> {
48+
private InputStream inputStream;
49+
private Class<T> clazz;
50+
private Integer sheetIndex;
51+
/**
52+
* 首行是否存在表头
53+
*/
54+
private Boolean hasHead;
55+
private ExcelRowHandler<T> rowHandler;
56+
/**
57+
* 根据首行表头获取的列序和表头映射关系
58+
* 当不存在表头时,存储的是列序和列序的字符串形式
59+
*/
60+
private Map<Integer, String> indexHeadMap;
61+
/**
62+
* 根据注解 headAlias 获取的表头和字段名的映射关系
63+
* 不存在 headAlias 时根据 colIndex 和 indexHeadMap 进行合并得出
64+
*/
65+
private Map<String, String> headFieldMap = new HashMap<>();
66+
/**
67+
* 需要忽略的字段,没有 ExcelField 注解的 / 注解 ignore 为 true 的
68+
*/
69+
private List<String> ignoredFields = new ArrayList<>();
70+
/**
71+
* BeanUtil 拷贝配置项
72+
*/
73+
private CopyOptions copyOptions = CopyOptions.create();
74+
75+
public static <T> ExcelReadHelper<T> create(InputStream inputStream, Class<T> clazz) {
76+
ExcelReadHelper<T> reader = new ExcelReadHelper<>();
77+
reader.setInputStream(inputStream);
78+
reader.setClazz(clazz);
79+
return reader;
80+
}
81+
82+
// builder methods
83+
84+
public ExcelReadHelper<T> sheetIndex(Integer sheetIndex) {
85+
setSheetIndex(sheetIndex);
86+
return this;
87+
}
88+
89+
public ExcelReadHelper<T> hasHead(Boolean hasHead) {
90+
setHasHead(hasHead);
91+
return this;
92+
}
93+
94+
public ExcelReadHelper<T> rowHandler(ExcelRowHandler<T> rowHandler) {
95+
setRowHandler(rowHandler);
96+
return this;
97+
}
98+
99+
public void read() {
100+
checkAndInitParams();
101+
// hutool sax read
102+
ExcelSaxReader<?> reader = ExcelSaxUtil.createSaxReader(ExcelFileUtil.isXlsx(inputStream), new RowHandler() {
103+
@Override
104+
public void handle(int sheetIndex, long rowIndex, List<Object> rowCells) {
105+
try {
106+
// 判断是否要根据首行获取 indexHeadMap
107+
if (initIndexHeadMap(rowCells)) {
108+
geneHeadFieldMap();
109+
geneCopyOptions();
110+
// 存在 head 第一行解析完成后 return
111+
if (ObjectUtil.equal(hasHead, true)) {
112+
return;
113+
}
114+
}
115+
// rowCells 转为 Map 结构
116+
Map<String, Object> rowMap = cellsToMap(rowCells);
117+
T bean = BeanUtil.toBean(rowMap, clazz, copyOptions);
118+
rowHandler.handle(sheetIndex, rowIndex, bean);
119+
} catch (Exception e) {
120+
rowHandler.onFailed(sheetIndex, rowIndex, e);
121+
}
122+
}
123+
124+
@Override
125+
public void doAfterAllAnalysed() {
126+
rowHandler.doAfterAll();
127+
}
128+
});
129+
reader.read(inputStream, sheetIndex);
130+
}
131+
132+
private void checkAndInitParams() {
133+
if (ObjectUtil.isNull(sheetIndex)) {
134+
// 默认读取首个 sheet
135+
sheetIndex = 0;
136+
}
137+
if (ObjectUtil.isNull(hasHead)) {
138+
// 默认有表头
139+
hasHead = true;
140+
}
141+
if (ObjectUtil.isNull(rowHandler)) {
142+
throw new RuntimeException("Can not read without rowHandler");
143+
}
144+
}
145+
146+
private boolean initIndexHeadMap(List<Object> rowCells) {
147+
if (ObjectUtil.isNotNull(indexHeadMap)) {
148+
return false;
149+
}
150+
AtomicInteger tmpIdx = new AtomicInteger();
151+
indexHeadMap = rowCells.stream()
152+
.collect(Collectors.toMap(o -> tmpIdx.getAndIncrement(), o -> {
153+
// 有表头时使用表头映射
154+
if (ObjectUtil.equal(hasHead, true)) {
155+
return String.valueOf(o);
156+
}
157+
// 无表头时使用列序映射
158+
return String.valueOf(tmpIdx.get() - 1);
159+
}));
160+
return true;
161+
}
162+
163+
private void geneHeadFieldMap() {
164+
for (Field field : clazz.getDeclaredFields()) {
165+
field.setAccessible(true);
166+
ExcelField excelField = field.getAnnotation(ExcelField.class);
167+
// 若不存在 ExcelField 注解,加入忽略
168+
if (ObjectUtil.isNull(excelField)) {
169+
ignoredFields.add(field.getName());
170+
continue;
171+
}
172+
// 若开启读忽略,加入忽略
173+
if (excelField.readIgnore()) {
174+
ignoredFields.add(field.getName());
175+
continue;
176+
}
177+
// 若存在表头,且 headAlias 不为空
178+
if (hasHead && ObjectUtil.isNotEmpty(excelField.headAlias())) {
179+
headFieldMap.put(excelField.headAlias(), field.getName());
180+
continue;
181+
}
182+
// 若存在 ExcelField 注解,但是不存在表头或 headAlias
183+
// 判断是否设置 colIndex,是否溢出
184+
if (excelField.colIndex() != -1 && indexHeadMap.size() > excelField.colIndex()) {
185+
// 从 indexHeadMap 获取对应的 head,putIfAbsent 不允许覆盖
186+
headFieldMap.putIfAbsent(indexHeadMap.get(excelField.colIndex()), field.getName());
187+
}
188+
}
189+
}
190+
191+
private void geneCopyOptions() {
192+
copyOptions.setIgnoreProperties(ignoredFields.toArray(new String[0]))
193+
.setFieldMapping(headFieldMap);
194+
}
195+
196+
private Map<String, Object> cellsToMap(List<Object> rowCells) {
197+
AtomicInteger tmpIdx = new AtomicInteger();
198+
return rowCells.stream()
199+
.collect(Collectors.toMap(o -> indexHeadMap.get(tmpIdx.getAndIncrement()), o -> o, (k1, k2) -> k1));
200+
}
201+
202+
public List<T> readAll() {
203+
List<T> beanList = new ArrayList<>();
204+
ExcelReadHelper<T> readHelper = create(inputStream, clazz)
205+
.sheetIndex(sheetIndex)
206+
.hasHead(hasHead);
207+
ExcelRowHandler<T> newRowHandler;
208+
if (ObjectUtil.isNull(rowHandler)) {
209+
readHelper.rowHandler((sheetIndex, rowIndex, bean) -> beanList.add(bean)).read();
210+
return beanList;
211+
}
212+
// 复用设置的 rowHandler
213+
newRowHandler = new ExcelRowHandler<T>() {
214+
@Override
215+
public void doAfterAll() {
216+
rowHandler.doAfterAll();
217+
}
218+
219+
@Override
220+
public void onFailed(int sheetIndex, long rowIndex, Exception e) {
221+
rowHandler.onFailed(sheetIndex, rowIndex, e);
222+
}
223+
224+
@Override
225+
public void handle(int sheetIndex, long rowIndex, T bean) {
226+
rowHandler.handle(sheetIndex, rowIndex, bean);
227+
beanList.add(bean);
228+
}
229+
};
230+
readHelper.rowHandler(newRowHandler).read();
231+
return beanList;
232+
}
233+
}
234+
235+
/**
236+
* Excel 行处理器
237+
* onFailed 默认抛出包装的 RuntimeException
238+
* 如果需要异常时不中断处理,重写 onFailed 方法即可
239+
*/
240+
public interface ExcelRowHandler<T> {
241+
242+
void handle(int sheetIndex, long rowIndex, T bean);
243+
244+
default void doAfterAll() {
245+
}
246+
247+
default void onFailed(int sheetIndex, long rowIndex, Exception e) {
248+
throw new RuntimeException(StrUtil.format("Excel row handler failed at sheet:{} row:{}", sheetIndex, rowIndex), e);
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)