Skip to content

Commit 488989d

Browse files
committed
refactor: 优化excel导入导出
1 parent 1ee2c16 commit 488989d

8 files changed

Lines changed: 672 additions & 87 deletions

File tree

jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/excel/ExcelUtils.java

Lines changed: 361 additions & 50 deletions
Large diffs are not rendered by default.

jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/excel/ImportHelper.java

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import lombok.Getter;
2424
import lombok.RequiredArgsConstructor;
2525
import org.apache.commons.collections4.CollectionUtils;
26+
import org.apache.commons.collections4.MapUtils;
2627
import org.hswebframework.ezorm.core.CastUtil;
2728
import org.hswebframework.reactor.excel.CellDataType;
2829
import org.hswebframework.reactor.excel.ExcelHeader;
@@ -71,6 +72,11 @@ public class ImportHelper<T> {
7172
*/
7273
private int bufferSize = 200;
7374

75+
/**
76+
* 自定义buffer逻辑
77+
*/
78+
private Function<Flux<Importing<T>>, Flux<? extends Collection<Importing<T>>>> bufferFunction;
79+
7480
/**
7581
* 当批量处理失败时,是否回退为单条数据处理.
7682
*/
@@ -118,6 +124,12 @@ public ImportHelper<T> bufferSize(int bufferSize) {
118124
return this;
119125
}
120126

127+
public ImportHelper<T> buffer(Function<Flux<Importing<T>>, Flux<? extends Collection<Importing<T>>>> function) {
128+
this.bufferFunction = function;
129+
return this;
130+
}
131+
132+
121133
public ImportHelper<T> afterReadValidate(Class<?>... group) {
122134
return afterRead(t -> {
123135
if (t instanceof Entity) {
@@ -141,16 +153,23 @@ private List<ExcelHeader> createHeaders() {
141153
return headers;
142154
}
143155

156+
protected Flux<? extends Collection<Importing<T>>> doBuffer(Flux<Importing<T>> stream) {
157+
if (bufferFunction != null) {
158+
return bufferFunction.apply(stream);
159+
}
160+
return stream.buffer(bufferSize);
161+
}
162+
144163
@SuppressWarnings("all")
145164
public <R> Flux<R> doImportJson(Flux<DataBuffer> inputStream,
146165
Function<Importing<T>, R> resultMapper,
147166
Function<Flux<DataBuffer>, Mono<R>> infoWriter) {
148167

149168
Flux<Importing<T>> importingFlux = ObjectMappers
150169
.parseJsonStream(inputStream, Map.class)
151-
.map(map -> CastUtil.<Map<String,Object>>cast(map))
170+
.map(map -> CastUtil.<Map<String, Object>>cast(map))
152171
.index(this::createImporting)
153-
.buffer(bufferSize)
172+
.as(this::doBuffer)
154173
.concatMap(buffer -> this
155174
.executeImport(Collections2.filter(buffer, Importing::isSuccess))
156175
.thenMany(Flux.fromIterable(buffer)));
@@ -197,15 +216,19 @@ public <R> Flux<R> doImport(InputStream inputStream,
197216
String format,
198217
Function<Importing<T>, R> resultMapper,
199218
Function<Flux<DataBuffer>, Mono<R>> infoWriter) {
200-
if (FORMAT_JSON.equals(format)) {
201-
return doImportJson(DataBufferUtils.readInputStream(
202-
() -> inputStream,
203-
new NettyDataBufferFactory(ByteBufAllocator.DEFAULT),
204-
256 * 1024),
205-
resultMapper,
206-
infoWriter);
207-
}
208-
return doImport0(readExcelFile(inputStream, format), format, resultMapper, infoWriter);
219+
return Flux
220+
.defer(() -> {
221+
if (FORMAT_JSON.equals(format)) {
222+
return doImportJson(DataBufferUtils.readInputStream(
223+
() -> inputStream,
224+
new NettyDataBufferFactory(ByteBufAllocator.DEFAULT),
225+
256 * 1024),
226+
resultMapper,
227+
infoWriter);
228+
}
229+
return doImport0(readExcelFile(inputStream, format), format, resultMapper, infoWriter);
230+
})
231+
.as(LocaleUtils::transform);
209232
}
210233

211234
@SuppressWarnings("all")
@@ -216,13 +239,12 @@ protected Class<T> getInstanceType() {
216239
public Flux<Importing<T>> readExcelFile(InputStream inputStream, String format) {
217240

218241
return ExcelUtils
219-
.<Map<String, Object>>read(LinkedHashMap::new,
220-
createHeaders(),
221-
inputStream,
222-
format)
223-
.map(data -> Maps.filterValues(data, obj -> !ObjectUtils.isEmpty(obj)))
224-
.index(this::createImporting)
225-
.buffer(bufferSize)
242+
.read0(this::createImporting,
243+
createHeaders(),
244+
inputStream,
245+
format)
246+
.filter(e -> MapUtils.isNotEmpty(e.getSource()))
247+
.as(this::doBuffer)
226248
.concatMap(buffer -> this
227249
.executeImport(Collections2.filter(buffer, Importing::isSuccess))
228250
.thenMany(Flux.fromIterable(buffer)));
@@ -262,15 +284,21 @@ private Mono<Void> executeImport(Collection<Importing<T>> buffer) {
262284
}
263285

264286
private Importing<T> createImporting(long index, Map<String, Object> data) {
287+
return createImporting(index, data, data);
288+
}
289+
290+
private Importing<T> createImporting(long index,
291+
Map<String, Object> source,
292+
Map<String, Object> converted) {
265293
T instance = instanceSupplier.get();
266-
Importing<T> importing = new Importing<>(index, data, instance);
294+
Importing<T> importing = new Importing<>(index, Maps.filterValues(source, obj -> !ObjectUtils.isEmpty(obj)), instance);
267295
try {
268296
if (instance instanceof Entity) {
269-
((Entity) instance).copyFrom(data);
297+
((Entity) instance).copyFrom(converted);
270298
} else if (instance instanceof Jsonable) {
271-
((Jsonable) instance).fromJson(new JSONObject(data));
299+
((Jsonable) instance).fromJson(new JSONObject(converted));
272300
} else {
273-
FastBeanCopier.copy(data, instance);
301+
FastBeanCopier.copy(converted, instance);
274302
}
275303
if (afterRead != null) {
276304
afterRead.accept(instance);
@@ -298,21 +326,34 @@ public String getErrorMessage() {
298326
//todo 更多异常信息判断
299327
if (error instanceof NumberFormatException) {
300328
String msg = error.getMessage();
301-
if (null == msg){
329+
if (null == msg) {
302330
return LocaleUtils.resolveMessage("error.number_format_error_no_arg", "无法转换为数字");
303331
}
304332
if (msg.contains("\"")) {
305333
String ch = msg.substring(msg.indexOf("\"") + 1, msg.lastIndexOf("\""));
306334
return LocaleUtils.resolveMessage("error.number_format_error", "无法转换[" + ch + "]为数字", ch);
307335
}
308336
}
309-
return error == null ? null : error.getLocalizedMessage();
337+
return error == null ? null : (
338+
error.getLocalizedMessage() == null ? error.getClass().getSimpleName() : error.getLocalizedMessage()
339+
);
310340
}
311341

312342
void error(Throwable error) {
313343
this.success = false;
314344
this.error = error;
315345
}
316-
}
317346

347+
@Override
348+
public String toString() {
349+
if (success || error == null) {
350+
return row + " =>" + source + " => " + target;
351+
}
352+
try {
353+
return row + " =>" + source + " error:" + getErrorMessage();
354+
} catch (Throwable e) {
355+
return row + " => error:" + getErrorMessage();
356+
}
357+
}
358+
}
318359
}

jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/excel/annotation/ExcelHeader.java

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,36 @@
1616
package org.jetlinks.community.io.excel.annotation;
1717

1818
import io.swagger.v3.oas.annotations.media.Schema;
19+
import org.apache.poi.ss.usermodel.BorderStyle;
20+
import org.apache.poi.ss.usermodel.HorizontalAlignment;
21+
import org.apache.poi.ss.usermodel.IndexedColors;
22+
import org.apache.poi.ss.usermodel.VerticalAlignment;
1923
import org.hswebframework.reactor.excel.CellDataType;
2024
import org.hswebframework.reactor.excel.ExcelOption;
2125
import org.jetlinks.community.io.excel.converter.ConverterExcelOption;
26+
import reactor.core.publisher.Flux;
2227

28+
import java.io.InputStream;
2329
import java.lang.annotation.ElementType;
2430
import java.lang.annotation.Inherited;
2531
import java.lang.annotation.Retention;
2632
import java.lang.annotation.RetentionPolicy;
2733
import java.lang.annotation.Target;
2834
import java.lang.reflect.Field;
35+
import java.util.function.Supplier;
2936

30-
@Target({ElementType.FIELD,ElementType.METHOD})
37+
/**
38+
* 在实体类中字段或者getter方法上添加此注解,以声明表头信息
39+
*
40+
* @author zhouhao
41+
* @see org.jetlinks.community.io.excel.ExcelUtils#write(Class, Flux, String, ExcelOption...)
42+
* @see org.jetlinks.community.io.excel.ExcelUtils#read(Supplier, InputStream, String, ExcelOption...)
43+
* @see org.jetlinks.community.io.excel.ExcelUtils#getHeadersForWrite(Class)
44+
* @see org.jetlinks.community.io.excel.ExcelUtils#getHeadersForRead(Class)
45+
* @see org.jetlinks.community.io.excel.AbstractImporter
46+
* @since 2.1
47+
*/
48+
@Target({ElementType.FIELD, ElementType.METHOD})
3149
@Retention(RetentionPolicy.RUNTIME)
3250
@Inherited
3351
public @interface ExcelHeader {
@@ -39,6 +57,11 @@
3957
*/
4058
String[] value() default {};
4159

60+
/**
61+
* @return 表头国际化code
62+
*/
63+
String i18nCode() default "";
64+
4265
/**
4366
* @return 忽略导入导出
4467
*/
@@ -79,4 +102,83 @@
79102
*/
80103
Class<? extends ExcelOption>[] options() default {};
81104

105+
/**
106+
* excel表头样式
107+
*
108+
* @return 表头样式
109+
* @since 2.2
110+
*/
111+
Style headerStyle() default @Style;
112+
113+
/**
114+
* excel数据样式
115+
*
116+
* @return 数据样式
117+
* @since 2.2
118+
*/
119+
Style dataStyle() default @Style;
120+
121+
/**
122+
* 全局样式,表头和数据都会应用
123+
*
124+
* @return 2.2
125+
*/
126+
Style style() default @Style;
127+
128+
@Target({ElementType.PARAMETER, ElementType.TYPE})
129+
@Retention(RetentionPolicy.RUNTIME)
130+
@Inherited
131+
@interface Style {
132+
133+
/**
134+
* 列宽度 1-255
135+
*
136+
* @return 列宽度
137+
*/
138+
short width() default -1;
139+
140+
/**
141+
* 高度
142+
*
143+
* @return 高度
144+
*/
145+
short height() default -1;
146+
147+
/**
148+
* 字体颜色
149+
*
150+
* @return 字体颜色
151+
*/
152+
IndexedColors fontColor() default IndexedColors.AUTOMATIC;
153+
154+
/**
155+
* 字体大小
156+
*
157+
* @return 字体大小
158+
*/
159+
byte fontSize() default -1;
160+
161+
/**
162+
* 边框样式: {上,右,下,左}
163+
*
164+
* @return 边框样式
165+
*/
166+
BorderStyle[] border() default {};
167+
168+
/**
169+
* 水平对齐方法
170+
*
171+
* @return 对齐方法
172+
*/
173+
HorizontalAlignment align() default HorizontalAlignment.GENERAL;
174+
175+
/**
176+
* 垂直对齐方式
177+
*
178+
* @return 对齐方式
179+
*/
180+
VerticalAlignment verticalAlign() default VerticalAlignment.BOTTOM;
181+
182+
}
183+
82184
}

jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/excel/converter/ArrayConverter.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@
2222

2323
import java.util.List;
2424

25-
/**
26-
* @since 2.1
27-
*/
2825
@AllArgsConstructor
29-
public class ArrayConverter implements ConverterExcelOption{
26+
public class ArrayConverter implements ConverterExcelOption {
3027

3128
private boolean array;
3229

@@ -38,12 +35,12 @@ public class ArrayConverter implements ConverterExcelOption{
3835
@Override
3936
public Object convertForWrite(Object val, ExcelHeader header) {
4037
return String.join(",",
41-
ConverterUtils.convertToList(val, v -> {
42-
if (converter == null) {
43-
return String.valueOf(v);
44-
}
45-
return String.valueOf(converter.convertForWrite(v, header));
46-
}));
38+
ConverterUtils.convertToList(val, v -> {
39+
if (converter == null) {
40+
return String.valueOf(v);
41+
}
42+
return String.valueOf(converter.convertForWrite(v, header));
43+
}));
4744
}
4845

4946
@Override

jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/excel/converter/ConverterExcelOption.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
import org.hswebframework.reactor.excel.ExcelOption;
2020

2121
/**
22+
* excel数据转换操作,用于自定义数据转换.
23+
*
24+
* @author zhouhao
25+
* @see org.jetlinks.community.io.excel.annotation.ExcelHeader#converter()
2226
* @since 2.1
2327
*/
2428
public interface ConverterExcelOption extends ExcelOption {

jetlinks-components/io-component/src/main/java/org/jetlinks/community/io/excel/converter/DateConverter.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.apache.poi.ss.usermodel.DataFormat;
2121
import org.hswebframework.reactor.excel.ExcelHeader;
2222
import org.hswebframework.reactor.excel.WritableCell;
23+
import org.hswebframework.reactor.excel.context.Context;
2324
import org.hswebframework.reactor.excel.poi.options.CellOption;
2425
import org.jetlinks.reactor.ql.utils.CastUtils;
2526
import org.joda.time.DateTime;
@@ -67,10 +68,39 @@ public Object convertForRead(Object val, ExcelHeader header) {
6768
return date;
6869
}
6970

71+
public void cell(org.apache.poi.ss.usermodel.Cell poiCell, WritableCell cell, Context context) {
72+
73+
short fmt = context
74+
.computeIfAbsent("fmt_" + format, (ignore) -> {
75+
DataFormat dataFormat = poiCell
76+
.getRow()
77+
.getSheet()
78+
.getWorkbook()
79+
.createDataFormat();
80+
return dataFormat.getFormat(format);
81+
});
82+
83+
CellStyle style = poiCell.getCellStyle();
84+
85+
if (style.getIndex() == 0) {
86+
style = context
87+
.computeIfAbsent("fmt_s_" + format + ":" + cell.getColumnIndex(), (ignore) -> poiCell
88+
.getRow()
89+
.getSheet()
90+
.getWorkbook()
91+
.createCellStyle());
92+
poiCell.setCellStyle(style);
93+
}
94+
95+
style.setDataFormat(fmt);
96+
97+
98+
}
99+
70100
@Override
71101
public void cell(org.apache.poi.ss.usermodel.Cell poiCell, WritableCell cell) {
72102
CellStyle style = poiCell.getCellStyle();
73-
if (style == null) {
103+
if (style.getIndex() == 0) {
74104
style = poiCell.getRow()
75105
.getSheet()
76106
.getWorkbook()

0 commit comments

Comments
 (0)