diff --git a/README.md b/README.md index fd5c5a2f..1251ebd2 100644 --- a/README.md +++ b/README.md @@ -62,16 +62,19 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java) ```java /** - * 文件下载 + * 文件下载(失败了会返回一个有部分数据的Excel) *

1. 创建excel对应的实体对象 参照{@link DownloadData} *

2. 设置返回的 参数 *

3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { + // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); - response.setHeader("Content-disposition", "attachment;filename=demo.xlsx"); + // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 + String fileName = URLEncoder.encode("测试", "UTF-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); } @@ -84,7 +87,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { - EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead(); + EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; } ``` diff --git a/img/readme/quickstart/fill/complexFill.png b/img/readme/quickstart/fill/complexFill.png deleted file mode 100644 index d37bb0f9..00000000 Binary files a/img/readme/quickstart/fill/complexFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/complexFillTemplate.png b/img/readme/quickstart/fill/complexFillTemplate.png deleted file mode 100644 index 14e26b95..00000000 Binary files a/img/readme/quickstart/fill/complexFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/complexFillWithTable.png b/img/readme/quickstart/fill/complexFillWithTable.png deleted file mode 100644 index c1075201..00000000 Binary files a/img/readme/quickstart/fill/complexFillWithTable.png and /dev/null differ diff --git a/img/readme/quickstart/fill/complexFillWithTableTemplate.png b/img/readme/quickstart/fill/complexFillWithTableTemplate.png deleted file mode 100644 index 902cd0da..00000000 Binary files a/img/readme/quickstart/fill/complexFillWithTableTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/horizontalFill.png b/img/readme/quickstart/fill/horizontalFill.png deleted file mode 100644 index 10f90cd7..00000000 Binary files a/img/readme/quickstart/fill/horizontalFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/horizontalFillTemplate.png b/img/readme/quickstart/fill/horizontalFillTemplate.png deleted file mode 100644 index 462b86eb..00000000 Binary files a/img/readme/quickstart/fill/horizontalFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/listFill.png b/img/readme/quickstart/fill/listFill.png deleted file mode 100644 index 3baca5a8..00000000 Binary files a/img/readme/quickstart/fill/listFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/listFillTemplate.png b/img/readme/quickstart/fill/listFillTemplate.png deleted file mode 100644 index 010c8c16..00000000 Binary files a/img/readme/quickstart/fill/listFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/fill/simpleFill.png b/img/readme/quickstart/fill/simpleFill.png deleted file mode 100644 index 4f575628..00000000 Binary files a/img/readme/quickstart/fill/simpleFill.png and /dev/null differ diff --git a/img/readme/quickstart/fill/simpleFillTemplate.png b/img/readme/quickstart/fill/simpleFillTemplate.png deleted file mode 100644 index 97a9119d..00000000 Binary files a/img/readme/quickstart/fill/simpleFillTemplate.png and /dev/null differ diff --git a/img/readme/quickstart/read/demo.png b/img/readme/quickstart/read/demo.png deleted file mode 100644 index 8bd14139..00000000 Binary files a/img/readme/quickstart/read/demo.png and /dev/null differ diff --git a/img/readme/quickstart/write/complexHeadWrite.png b/img/readme/quickstart/write/complexHeadWrite.png deleted file mode 100644 index 995ffc3c..00000000 Binary files a/img/readme/quickstart/write/complexHeadWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/converterWrite.png b/img/readme/quickstart/write/converterWrite.png deleted file mode 100644 index 5c9f2898..00000000 Binary files a/img/readme/quickstart/write/converterWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/customHandlerWrite.png b/img/readme/quickstart/write/customHandlerWrite.png deleted file mode 100644 index 41916c3f..00000000 Binary files a/img/readme/quickstart/write/customHandlerWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/dynamicHeadWrite.png b/img/readme/quickstart/write/dynamicHeadWrite.png deleted file mode 100644 index 3b25ad72..00000000 Binary files a/img/readme/quickstart/write/dynamicHeadWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/imageWrite.png b/img/readme/quickstart/write/imageWrite.png deleted file mode 100644 index c6a0c67a..00000000 Binary files a/img/readme/quickstart/write/imageWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/indexWrite.png b/img/readme/quickstart/write/indexWrite.png deleted file mode 100644 index 8dbec177..00000000 Binary files a/img/readme/quickstart/write/indexWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/longestMatchColumnWidthWrite.png b/img/readme/quickstart/write/longestMatchColumnWidthWrite.png deleted file mode 100644 index c8a06049..00000000 Binary files a/img/readme/quickstart/write/longestMatchColumnWidthWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/mergeWrite.png b/img/readme/quickstart/write/mergeWrite.png deleted file mode 100644 index b631e59b..00000000 Binary files a/img/readme/quickstart/write/mergeWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/repeatedWrite.png b/img/readme/quickstart/write/repeatedWrite.png deleted file mode 100644 index fb204a4b..00000000 Binary files a/img/readme/quickstart/write/repeatedWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/simpleWrite.png b/img/readme/quickstart/write/simpleWrite.png deleted file mode 100644 index e5924b19..00000000 Binary files a/img/readme/quickstart/write/simpleWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/styleWrite.png b/img/readme/quickstart/write/styleWrite.png deleted file mode 100644 index 356c3a7f..00000000 Binary files a/img/readme/quickstart/write/styleWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/tableWrite.png b/img/readme/quickstart/write/tableWrite.png deleted file mode 100644 index 0006a9ea..00000000 Binary files a/img/readme/quickstart/write/tableWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/templateWrite.png b/img/readme/quickstart/write/templateWrite.png deleted file mode 100644 index 75f7cb16..00000000 Binary files a/img/readme/quickstart/write/templateWrite.png and /dev/null differ diff --git a/img/readme/quickstart/write/widthAndHeightWrite.png b/img/readme/quickstart/write/widthAndHeightWrite.png deleted file mode 100644 index d59d8972..00000000 Binary files a/img/readme/quickstart/write/widthAndHeightWrite.png and /dev/null differ diff --git a/img/readme/wechat.png b/img/readme/wechat.png deleted file mode 100644 index b8057604..00000000 Binary files a/img/readme/wechat.png and /dev/null differ diff --git a/pom.xml b/pom.xml index a6183490..7dad24da 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.alibaba easyexcel - 2.1.0-beta4 + 2.1.1 jar easyexcel diff --git a/src/main/java/com/alibaba/excel/ExcelReader.java b/src/main/java/com/alibaba/excel/ExcelReader.java index 0887e325..07a373b7 100644 --- a/src/main/java/com/alibaba/excel/ExcelReader.java +++ b/src/main/java/com/alibaba/excel/ExcelReader.java @@ -14,7 +14,6 @@ import com.alibaba.excel.analysis.ExcelReadExecutor; import com.alibaba.excel.cache.MapCache; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; -import com.alibaba.excel.exception.ExcelAnalysisException; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.parameter.AnalysisParam; import com.alibaba.excel.read.listener.ReadListener; @@ -35,8 +34,6 @@ public class ExcelReader { */ private ExcelAnalyser excelAnalyser; - private boolean finished = false; - /** * Create new reader * @@ -160,7 +157,6 @@ public class ExcelReader { * Parse all sheet content by default */ public void readAll() { - checkFinished(); excelAnalyser.analysis(null, Boolean.TRUE); } @@ -181,7 +177,6 @@ public class ExcelReader { * @return */ public ExcelReader read(List readSheetList) { - checkFinished(); excelAnalyser.analysis(readSheetList, Boolean.FALSE); return this; } @@ -231,7 +226,6 @@ public class ExcelReader { * @return */ public AnalysisContext analysisContext() { - checkFinished(); return excelAnalyser.analysisContext(); } @@ -241,7 +235,6 @@ public class ExcelReader { * @return */ public ExcelReadExecutor excelExecutor() { - checkFinished(); return excelAnalyser.excelExecutor(); } @@ -281,10 +274,6 @@ public class ExcelReader { * Complete the entire read file.Release the cache and close stream. */ public void finish() { - if (finished) { - return; - } - finished = true; excelAnalyser.finish(); } @@ -294,19 +283,11 @@ public class ExcelReader { */ @Override protected void finalize() { - if (finished) { - return; - } try { - excelAnalyser.finish(); + finish(); } catch (Throwable e) { LOGGER.warn("Destroy object failed", e); } } - private void checkFinished() { - if (finished) { - throw new ExcelAnalysisException("Can not use a finished reader."); - } - } } diff --git a/src/main/java/com/alibaba/excel/ExcelWriter.java b/src/main/java/com/alibaba/excel/ExcelWriter.java index 65a99fe5..a0b4795b 100644 --- a/src/main/java/com/alibaba/excel/ExcelWriter.java +++ b/src/main/java/com/alibaba/excel/ExcelWriter.java @@ -5,6 +5,9 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.metadata.Table; @@ -31,6 +34,8 @@ import com.alibaba.excel.write.metadata.fill.FillConfig; * @author jipengfei */ public class ExcelWriter { + private static final Logger LOGGER = LoggerFactory.getLogger(ExcelWriter.class); + private ExcelBuilder excelBuilder; /** @@ -320,7 +325,20 @@ public class ExcelWriter { * Close IO */ public void finish() { - excelBuilder.finish(); + excelBuilder.finish(false); + } + + /** + * Prevents calls to {@link #finish} from freeing the cache + * + */ + @Override + protected void finalize() { + try { + finish(); + } catch (Throwable e) { + LOGGER.warn("Destroy object failed", e); + } } /** diff --git a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java index 72a27e7f..f3ef9505 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -1,13 +1,10 @@ package com.alibaba.excel.analysis; -import java.io.FileInputStream; import java.io.InputStream; import java.util.List; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.crypt.Decryptor; -import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.filesystem.DocumentFactoryHelper; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.IOUtils; @@ -37,6 +34,10 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { private AnalysisContext analysisContext; private ExcelReadExecutor excelReadExecutor; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; public ExcelAnalyserImpl(ReadWorkbook readWorkbook) { try { @@ -124,6 +125,10 @@ public class ExcelAnalyserImpl implements ExcelAnalyser { @Override public void finish() { + if (finished) { + return; + } + finished = true; if (analysisContext == null || analysisContext.readWorkbookHolder() == null) { return; } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java index 21977a4d..e0c150cd 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -3,6 +3,7 @@ package com.alibaba.excel.analysis.v03; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -26,6 +27,7 @@ import com.alibaba.excel.analysis.ExcelReadExecutor; import com.alibaba.excel.analysis.v03.handlers.BlankOrErrorRecordHandler; import com.alibaba.excel.analysis.v03.handlers.BofRecordHandler; import com.alibaba.excel.analysis.v03.handlers.FormulaRecordHandler; +import com.alibaba.excel.analysis.v03.handlers.IndexRecordHandler; import com.alibaba.excel.analysis.v03.handlers.LabelRecordHandler; import com.alibaba.excel.analysis.v03.handlers.MissingCellDummyRecordHandler; import com.alibaba.excel.analysis.v03.handlers.NoteRecordHandler; @@ -77,7 +79,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { public XlsSaxAnalyser(AnalysisContext context, POIFSFileSystem poifsFileSystem) { this.analysisContext = context; - this.records = new TreeMap(); + this.records = new LinkedHashMap(); this.poifsFileSystem = poifsFileSystem; analysisContext.readWorkbookHolder().setPoifsFileSystem(poifsFileSystem); } @@ -120,7 +122,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { private void init() { lastRowNumber = 0; lastColumnNumber = 0; - records = new TreeMap(); + records = new LinkedHashMap(); buildXlsRecordHandlers(); } @@ -210,6 +212,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { recordHandlers.add(new RkRecordHandler()); recordHandlers.add(new SstRecordHandler()); recordHandlers.add(new MissingCellDummyRecordHandler()); + recordHandlers.add(new IndexRecordHandler(analysisContext)); Collections.sort(recordHandlers); } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java index 3a862942..083edbc7 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/BofRecordHandler.java @@ -73,7 +73,7 @@ public class BofRecordHandler extends AbstractXlsRecordHandler { readSheet = SheetUtils.match(readSheet, readSheetList, readAll, context.readWorkbookHolder().getGlobalConfiguration()); if (readSheet != null) { - if (readSheet.getSheetNo() != 0) { + if (readSheet.getSheetNo() != 0 && context.readSheetHolder() != null) { // Prompt for the end of the previous form read context.readSheetHolder().notifyAfterAllAnalysed(context); } diff --git a/src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java b/src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java new file mode 100644 index 00000000..6837ebd6 --- /dev/null +++ b/src/main/java/com/alibaba/excel/analysis/v03/handlers/IndexRecordHandler.java @@ -0,0 +1,42 @@ +package com.alibaba.excel.analysis.v03.handlers; + +import org.apache.poi.hssf.record.IndexRecord; +import org.apache.poi.hssf.record.Record; + +import com.alibaba.excel.analysis.v03.AbstractXlsRecordHandler; +import com.alibaba.excel.context.AnalysisContext; + +/** + * Record handler + * + * @author Jiaju Zhuang + */ +public class IndexRecordHandler extends AbstractXlsRecordHandler { + + private AnalysisContext context; + + public IndexRecordHandler(AnalysisContext context) { + this.context = context; + } + + @Override + public boolean support(Record record) { + return record instanceof IndexRecord; + } + + @Override + public void init() {} + + @Override + public void processRecord(Record record) { + if (context.readSheetHolder() == null) { + return; + } + context.readSheetHolder().setApproximateTotalRowNumber(((IndexRecord)record).getLastRowAdd1()); + } + + @Override + public int getOrder() { + return 1; + } +} diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java index ea63409b..b94483f4 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/CountRowCellHandler.java @@ -10,7 +10,7 @@ import com.alibaba.excel.context.AnalysisContext; /** * Cell Handler - * + * * @author jipengfei */ public class CountRowCellHandler implements XlsxCellHandler { @@ -31,7 +31,7 @@ public class CountRowCellHandler implements XlsxCellHandler { String d = attributes.getValue(DIMENSION_REF); String totalStr = d.substring(d.indexOf(":") + 1, d.length()); String c = totalStr.toUpperCase().replaceAll("[A-Z]", ""); - analysisContext.readSheetHolder().setTotal(Integer.parseInt(c)); + analysisContext.readSheetHolder().setApproximateTotalRowNumber(Integer.parseInt(c)); } @Override diff --git a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 0d44a95b..e0b9c413 100644 --- a/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java +++ b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java @@ -9,9 +9,9 @@ import static com.alibaba.excel.constant.ExcelXmlConstants.CELL_VALUE_TYPE_TAG; import java.math.BigDecimal; import java.util.Deque; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.Map; -import java.util.TreeMap; import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.xssf.model.StylesTable; @@ -37,7 +37,7 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder private final AnalysisContext analysisContext; private Deque currentTagDeque = new LinkedList(); private int curCol; - private Map curRowContent = new TreeMap(); + private Map curRowContent = new LinkedHashMap(); private CellData currentCellData; private StringBuilder dataStringBuilder; private StringBuilder formulaStringBuilder; @@ -54,7 +54,7 @@ public class DefaultCellHandler implements XlsxCellHandler, XlsxRowResultHolder @Override public void clearResult() { - curRowContent = new TreeMap(); + curRowContent = new LinkedHashMap(); } @Override diff --git a/src/main/java/com/alibaba/excel/context/WriteContext.java b/src/main/java/com/alibaba/excel/context/WriteContext.java index c6e85c94..0a59e1d0 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContext.java +++ b/src/main/java/com/alibaba/excel/context/WriteContext.java @@ -66,9 +66,10 @@ public interface WriteContext { /** * close + * + * @param onException */ - void finish(); - + void finish(boolean onException); /** * Current sheet diff --git a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java index fcb276cb..84205024 100644 --- a/src/main/java/com/alibaba/excel/context/WriteContextImpl.java +++ b/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -65,6 +65,10 @@ public class WriteContextImpl implements WriteContext { * Configuration of currently operated cell */ private WriteHolder currentWriteHolder; + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; public WriteContextImpl(WriteWorkbook writeWorkbook) { if (writeWorkbook == null) { @@ -249,23 +253,36 @@ public class WriteContextImpl implements WriteContext { } @Override - public void finish() { + public void finish(boolean onException) { + if (finished) { + return; + } + finished = true; WriteHandlerUtils.afterWorkbookDispose(this); if (writeWorkbookHolder == null) { return; } Throwable throwable = null; - boolean isOutputStreamEncrypt = false; - try { - isOutputStreamEncrypt = doOutputStreamEncrypt07(); - } catch (Throwable t) { - throwable = t; + // Determine if you need to write excel + boolean writeExcel = !onException; + if (writeWorkbookHolder.getWriteExcelOnException()) { + writeExcel = Boolean.TRUE; + } + // No data is written if an exception is thrown + if (writeExcel) { + try { + isOutputStreamEncrypt = doOutputStreamEncrypt07(); + } catch (Throwable t) { + throwable = t; + } } if (!isOutputStreamEncrypt) { try { - writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); + if (writeExcel) { + writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); + } writeWorkbookHolder.getWorkbook().close(); } catch (Throwable t) { throwable = t; @@ -289,7 +306,7 @@ public class WriteContextImpl implements WriteContext { throwable = t; } - if (!isOutputStreamEncrypt) { + if (writeExcel && !isOutputStreamEncrypt) { try { doFileEncrypt07(); } catch (Throwable t) { diff --git a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java index e53c1f85..77e8b592 100644 --- a/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java +++ b/src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java @@ -37,6 +37,7 @@ import com.alibaba.excel.converters.string.StringBooleanConverter; import com.alibaba.excel.converters.string.StringErrorConverter; import com.alibaba.excel.converters.string.StringNumberConverter; import com.alibaba.excel.converters.string.StringStringConverter; +import com.alibaba.excel.converters.url.UrlImageConverter; /** * Load default handler @@ -71,6 +72,7 @@ public class DefaultConverterLoader { putWriteConverter(new InputStreamImageConverter()); putWriteConverter(new ByteArrayImageConverter()); putWriteConverter(new BoxingByteArrayImageConverter()); + putWriteConverter(new UrlImageConverter()); return defaultWriteConverter; } diff --git a/src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java b/src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java new file mode 100644 index 00000000..b622d66d --- /dev/null +++ b/src/main/java/com/alibaba/excel/converters/url/UrlImageConverter.java @@ -0,0 +1,52 @@ +package com.alibaba.excel.converters.url; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.util.IoUtils; + +/** + * Url and image converter + * + * @since 2.1.1 + * @author Jiaju Zhuang + */ +public class UrlImageConverter implements Converter { + @Override + public Class supportJavaTypeKey() { + return URL.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.IMAGE; + } + + @Override + public URL convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + throw new UnsupportedOperationException("Cannot convert images to url."); + } + + @Override + public CellData convertToExcelData(URL value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws IOException { + InputStream inputStream = null; + try { + inputStream = value.openStream(); + byte[] bytes = IoUtils.toByteArray(inputStream); + return new CellData(bytes); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + +} diff --git a/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java b/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java index 89bca753..176bd798 100644 --- a/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java +++ b/src/main/java/com/alibaba/excel/event/AnalysisEventListener.java @@ -16,7 +16,7 @@ public abstract class AnalysisEventListener implements ReadListener { @Override public void invokeHead(Map headMap, AnalysisContext context) { - invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context.currentReadHolder()), context); + invokeHeadMap(ConverterUtils.convertToStringMap(headMap, context), context); } /** diff --git a/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java b/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java index 2ffdde5f..5198c20f 100644 --- a/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java +++ b/src/main/java/com/alibaba/excel/exception/ExcelDataConvertException.java @@ -1,5 +1,6 @@ package com.alibaba.excel.exception; +import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.property.ExcelContentProperty; import com.alibaba.excel.write.builder.ExcelWriterBuilder; @@ -17,6 +18,10 @@ public class ExcelDataConvertException extends RuntimeException { * NotNull. */ private Integer columnIndex; + /** + * NotNull. + */ + private CellData cellData; /** * Nullable.Only when the header is configured and when the class header is used is not null. * @@ -24,34 +29,24 @@ public class ExcelDataConvertException extends RuntimeException { */ private ExcelContentProperty excelContentProperty; - public ExcelDataConvertException(String message) { - super(message); - } - - public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, ExcelContentProperty excelContentProperty, - String message) { + public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData, + ExcelContentProperty excelContentProperty, String message) { super(message); this.rowIndex = rowIndex; this.columnIndex = columnIndex; + this.cellData = cellData; this.excelContentProperty = excelContentProperty; } - public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, ExcelContentProperty excelContentProperty, - String message, Throwable cause) { + public ExcelDataConvertException(Integer rowIndex, Integer columnIndex, CellData cellData, + ExcelContentProperty excelContentProperty, String message, Throwable cause) { super(message, cause); this.rowIndex = rowIndex; this.columnIndex = columnIndex; + this.cellData = cellData; this.excelContentProperty = excelContentProperty; } - public ExcelDataConvertException(String message, Throwable cause) { - super(message, cause); - } - - public ExcelDataConvertException(Throwable cause) { - super(cause); - } - public Integer getRowIndex() { return rowIndex; } @@ -75,4 +70,12 @@ public class ExcelDataConvertException extends RuntimeException { public void setExcelContentProperty(ExcelContentProperty excelContentProperty) { this.excelContentProperty = excelContentProperty; } + + public CellData getCellData() { + return cellData; + } + + public void setCellData(CellData cellData) { + this.cellData = cellData; + } } diff --git a/src/main/java/com/alibaba/excel/metadata/CellData.java b/src/main/java/com/alibaba/excel/metadata/CellData.java index a6b7af58..7cd5496e 100644 --- a/src/main/java/com/alibaba/excel/metadata/CellData.java +++ b/src/main/java/com/alibaba/excel/metadata/CellData.java @@ -229,18 +229,21 @@ public class CellData { @Override public String toString() { if (type == null) { - return "empty"; + return StringUtils.EMPTY; } switch (type) { case NUMBER: return numberValue.toString(); case BOOLEAN: return booleanValue.toString(); + case DIRECT_STRING: case STRING: case ERROR: return stringValue; + case IMAGE: + return "image[" + imageValue.length + "]"; default: - return "empty"; + return StringUtils.EMPTY; } } diff --git a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java index 9d6a5442..782f635a 100644 --- a/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java +++ b/src/main/java/com/alibaba/excel/metadata/property/ExcelHeadProperty.java @@ -24,6 +24,7 @@ import com.alibaba.excel.exception.ExcelCommonException; import com.alibaba.excel.exception.ExcelGenerateException; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.Holder; +import com.alibaba.excel.util.ClassUtils; import com.alibaba.excel.util.StringUtils; import com.alibaba.excel.write.metadata.holder.AbstractWriteHolder; @@ -119,51 +120,10 @@ public class ExcelHeadProperty { if (headClazz == null) { return; } - List fieldList = new ArrayList(); - Class tempClass = headClazz; - // When the parent class is null, it indicates that the parent class (Object class) has reached the top - // level. - while (tempClass != null) { - Collections.addAll(fieldList, tempClass.getDeclaredFields()); - // Get the parent class and give it to yourself - tempClass = tempClass.getSuperclass(); - } - - ExcelIgnoreUnannotated excelIgnoreUnannotated = - (ExcelIgnoreUnannotated)headClazz.getAnnotation(ExcelIgnoreUnannotated.class); - // Screening of field + // Declared fields List defaultFieldList = new ArrayList(); Map customFiledMap = new TreeMap(); - for (Field field : fieldList) { - ExcelIgnore excelIgnore = field.getAnnotation(ExcelIgnore.class); - if (excelIgnore != null) { - ignoreMap.put(field.getName(), field); - continue; - } - ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); - boolean noExcelProperty = excelProperty == null - && ((convertAllFiled != null && !convertAllFiled) || excelIgnoreUnannotated != null); - if (noExcelProperty) { - ignoreMap.put(field.getName(), field); - continue; - } - boolean isStaticFinalOrTransient = - (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) - || Modifier.isTransient(field.getModifiers()); - if (excelProperty == null && isStaticFinalOrTransient) { - ignoreMap.put(field.getName(), field); - continue; - } - if (excelProperty == null || excelProperty.index() < 0) { - defaultFieldList.add(field); - continue; - } - if (customFiledMap.containsKey(excelProperty.index())) { - throw new ExcelGenerateException("The index of '" + customFiledMap.get(excelProperty.index()).getName() - + "' and '" + field.getName() + "' must be inconsistent"); - } - customFiledMap.put(excelProperty.index(), field); - } + ClassUtils.declaredFields(headClazz, defaultFieldList, customFiledMap, ignoreMap, convertAllFiled); int index = 0; for (Field field : defaultFieldList) { diff --git a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java index af3b05e1..d494e233 100644 --- a/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java +++ b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java @@ -166,7 +166,7 @@ public class ExcelReaderSheetBuilder { * * @return */ - public List doReadSync() { + public List doReadSync() { if (excelReader == null) { throw new ExcelAnalysisException("Must use 'EasyExcelFactory.read().sheet()' to call this method"); } @@ -174,7 +174,7 @@ public class ExcelReaderSheetBuilder { registerReadListener(syncReadListener); excelReader.read(build()); excelReader.finish(); - return syncReadListener.getList(); + return (List)syncReadListener.getList(); } } diff --git a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java index a368024f..f5594db0 100644 --- a/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java +++ b/src/main/java/com/alibaba/excel/read/listener/ModelBuildEventListener.java @@ -2,6 +2,7 @@ package com.alibaba.excel.read.listener; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -44,7 +45,7 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener AnalysisContext context) { int index = 0; if (context.readWorkbookHolder().getDefaultReturnMap()) { - Map map = new HashMap(cellDataMap.size() * 4 / 3 + 1); + Map map = new LinkedHashMap(cellDataMap.size() * 4 / 3 + 1); for (Map.Entry entry : cellDataMap.entrySet()) { Integer key = entry.getKey(); CellData cellData = entry.getValue(); @@ -92,7 +93,8 @@ public class ModelBuildEventListener extends AbstractIgnoreExceptionReadListener try { resultModel = excelReadHeadProperty.getHeadClazz().newInstance(); } catch (Exception e) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 0, + new CellData(CellDataTypeEnum.EMPTY), null, "Can not instance class: " + excelReadHeadProperty.getHeadClazz().getName(), e); } Map headMap = excelReadHeadProperty.getHeadMap(); diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 157f90ee..02b2ef5f 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java @@ -190,8 +190,7 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) { return; } - Map dataMap = - ConverterUtils.convertToStringMap(cellDataMap, analysisContext.currentReadHolder()); + Map dataMap = ConverterUtils.convertToStringMap(cellDataMap, analysisContext); ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty(); Map headMapData = excelHeadPropertyData.getHeadMap(); Map contentPropertyMapData = excelHeadPropertyData.getContentPropertyMap(); @@ -208,6 +207,9 @@ public abstract class AbstractReadHolder extends AbstractHolder implements ReadH List headNameList = headData.getHeadNameList(); String headName = headNameList.get(headNameList.size() - 1); for (Map.Entry stringEntry : dataMap.entrySet()) { + if (stringEntry == null) { + continue; + } String headString = stringEntry.getValue(); Integer stringKey = stringEntry.getKey(); if (StringUtils.isEmpty(headString)) { diff --git a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java index 64d1d6cb..84dd4c4a 100644 --- a/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java +++ b/src/main/java/com/alibaba/excel/read/metadata/holder/ReadSheetHolder.java @@ -26,10 +26,9 @@ public class ReadSheetHolder extends AbstractReadHolder { */ private String sheetName; /** - * get total row , Data may be inaccurate + * Gets the total number of rows , data may be inaccurate */ - @Deprecated - private Integer total; + private Integer approximateTotalRowNumber; public ReadSheetHolder(ReadSheet readSheet, ReadWorkbookHolder readWorkbookHolder) { super(readSheet, readWorkbookHolder, readWorkbookHolder.getReadWorkbook().getConvertAllFiled()); @@ -71,12 +70,29 @@ public class ReadSheetHolder extends AbstractReadHolder { this.sheetName = sheetName; } + /** + * + * Approximate total number of rows + * + * @return + * @see #getApproximateTotalRowNumber() + */ + @Deprecated public Integer getTotal() { - return total; + return approximateTotalRowNumber; + } + + /** + * Approximate total number of rows + * + * @return + */ + public Integer getApproximateTotalRowNumber() { + return approximateTotalRowNumber; } - public void setTotal(Integer total) { - this.total = total; + public void setApproximateTotalRowNumber(Integer approximateTotalRowNumber) { + this.approximateTotalRowNumber = approximateTotalRowNumber; } @Override diff --git a/src/main/java/com/alibaba/excel/util/ClassUtils.java b/src/main/java/com/alibaba/excel/util/ClassUtils.java new file mode 100644 index 00000000..64bc29d7 --- /dev/null +++ b/src/main/java/com/alibaba/excel/util/ClassUtils.java @@ -0,0 +1,150 @@ +package com.alibaba.excel.util; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.exception.ExcelCommonException; +import com.alibaba.excel.metadata.BaseRowModel; + +/** + * Class utils + * + * @author Jiaju Zhuang + **/ +public class ClassUtils { + private static final Map> FIELD_CACHE = + new ConcurrentHashMap>(); + + public static void declaredFields(Class clazz, List defaultFieldList, Map customFiledMap, + Map ignoreMap, Boolean convertAllFiled) { + FieldCache fieldCache = getFieldCache(clazz, convertAllFiled); + if (fieldCache != null) { + defaultFieldList.addAll(fieldCache.getDefaultFieldList()); + customFiledMap.putAll(fieldCache.getCustomFiledMap()); + ignoreMap.putAll(fieldCache.getIgnoreMap()); + } + } + + public static void declaredFields(Class clazz, List fieldList, Boolean convertAllFiled) { + FieldCache fieldCache = getFieldCache(clazz, convertAllFiled); + if (fieldCache != null) { + fieldList.addAll(fieldCache.getAllFieldList()); + } + } + + private static FieldCache getFieldCache(Class clazz, Boolean convertAllFiled) { + if (clazz == null) { + return null; + } + SoftReference fieldCacheSoftReference = FIELD_CACHE.get(clazz); + if (fieldCacheSoftReference != null && fieldCacheSoftReference.get() != null) { + return fieldCacheSoftReference.get(); + } + synchronized (clazz) { + fieldCacheSoftReference = FIELD_CACHE.get(clazz); + if (fieldCacheSoftReference != null && fieldCacheSoftReference.get() != null) { + return fieldCacheSoftReference.get(); + } + declaredFields(clazz, convertAllFiled); + } + return FIELD_CACHE.get(clazz).get(); + } + + private static void declaredFields(Class clazz, Boolean convertAllFiled) { + List tempFieldList = new ArrayList(); + Class tempClass = clazz; + // When the parent class is null, it indicates that the parent class (Object class) has reached the top + // level. + while (tempClass != null && tempClass != BaseRowModel.class) { + Collections.addAll(tempFieldList, tempClass.getDeclaredFields()); + // Get the parent class and give it to yourself + tempClass = tempClass.getSuperclass(); + } + // Screening of field + List defaultFieldList = new ArrayList(); + Map customFiledMap = new TreeMap(); + List allFieldList = new ArrayList(); + Map ignoreMap = new HashMap(16); + + ExcelIgnoreUnannotated excelIgnoreUnannotated = + (ExcelIgnoreUnannotated)clazz.getAnnotation(ExcelIgnoreUnannotated.class); + for (Field field : tempFieldList) { + ExcelIgnore excelIgnore = field.getAnnotation(ExcelIgnore.class); + if (excelIgnore != null) { + ignoreMap.put(field.getName(), field); + continue; + } + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + boolean noExcelProperty = excelProperty == null + && ((convertAllFiled != null && !convertAllFiled) || excelIgnoreUnannotated != null); + if (noExcelProperty) { + ignoreMap.put(field.getName(), field); + continue; + } + boolean isStaticFinalOrTransient = + (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) + || Modifier.isTransient(field.getModifiers()); + if (excelProperty == null && isStaticFinalOrTransient) { + ignoreMap.put(field.getName(), field); + continue; + } + if (excelProperty == null || excelProperty.index() < 0) { + defaultFieldList.add(field); + allFieldList.add(field); + continue; + } + if (customFiledMap.containsKey(excelProperty.index())) { + throw new ExcelCommonException("The index of '" + customFiledMap.get(excelProperty.index()).getName() + + "' and '" + field.getName() + "' must be inconsistent"); + } + customFiledMap.put(excelProperty.index(), field); + allFieldList.add(field); + } + + FIELD_CACHE.put(clazz, + new SoftReference(new FieldCache(defaultFieldList, customFiledMap, allFieldList, ignoreMap))); + } + + private static class FieldCache { + private List defaultFieldList; + private Map customFiledMap; + private List allFieldList; + private Map ignoreMap; + + public FieldCache(List defaultFieldList, Map customFiledMap, List allFieldList, + Map ignoreMap) { + this.defaultFieldList = defaultFieldList; + this.customFiledMap = customFiledMap; + this.allFieldList = allFieldList; + this.ignoreMap = ignoreMap; + } + + public List getDefaultFieldList() { + return defaultFieldList; + } + + public Map getCustomFiledMap() { + return customFiledMap; + } + + public List getAllFieldList() { + return allFieldList; + } + + public Map getIgnoreMap() { + return ignoreMap; + } + + } +} diff --git a/src/main/java/com/alibaba/excel/util/ConverterUtils.java b/src/main/java/com/alibaba/excel/util/ConverterUtils.java index b6244c23..43b12c53 100644 --- a/src/main/java/com/alibaba/excel/util/ConverterUtils.java +++ b/src/main/java/com/alibaba/excel/util/ConverterUtils.java @@ -6,6 +6,7 @@ import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; +import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.converters.Converter; import com.alibaba.excel.converters.ConverterKeyBuild; import com.alibaba.excel.enums.CellDataTypeEnum; @@ -28,28 +29,37 @@ public class ConverterUtils { * Convert it into a String map * * @param cellDataMap - * @param readHolder + * @param context * @return */ - public static Map convertToStringMap(Map cellDataMap, ReadHolder readHolder) { + public static Map convertToStringMap(Map cellDataMap, AnalysisContext context) { Map stringMap = new HashMap(cellDataMap.size() * 4 / 3 + 1); + ReadHolder currentReadHolder = context.currentReadHolder(); + int index = 0; for (Map.Entry entry : cellDataMap.entrySet()) { + Integer key = entry.getKey(); CellData cellData = entry.getValue(); + while (index < key) { + stringMap.put(index, null); + index++; + } + index++; if (cellData.getType() == CellDataTypeEnum.EMPTY) { - stringMap.put(entry.getKey(), null); + stringMap.put(key, null); continue; } Converter converter = - readHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); + currentReadHolder.converterMap().get(ConverterKeyBuild.buildKey(String.class, cellData.getType())); if (converter == null) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), key, cellData, null, "Converter not found, convert " + cellData.getType() + " to String"); } try { - stringMap.put(entry.getKey(), - (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); + stringMap.put(key, + (String)(converter.convertToJavaData(cellData, null, currentReadHolder.globalConfiguration()))); } catch (Exception e) { - throw new ExcelDataConvertException("Convert data " + cellData + " to String error ", e); + throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), key, cellData, null, + "Convert data " + cellData + " to String error ", e); } } return stringMap; @@ -116,13 +126,13 @@ public class ConverterUtils { converter = converterMap.get(ConverterKeyBuild.buildKey(clazz, cellData.getType())); } if (converter == null) { - throw new ExcelDataConvertException(rowIndex, columnIndex, contentProperty, + throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty, "Converter not found, convert " + cellData.getType() + " to " + clazz.getName()); } try { return converter.convertToJavaData(cellData, contentProperty, globalConfiguration); } catch (Exception e) { - throw new ExcelDataConvertException(rowIndex, columnIndex, contentProperty, + throw new ExcelDataConvertException(rowIndex, columnIndex, cellData, contentProperty, "Convert data " + cellData + " to " + clazz + " error ", e); } } diff --git a/src/main/java/com/alibaba/excel/util/DateUtils.java b/src/main/java/com/alibaba/excel/util/DateUtils.java index 1ea05c0f..a581a955 100644 --- a/src/main/java/com/alibaba/excel/util/DateUtils.java +++ b/src/main/java/com/alibaba/excel/util/DateUtils.java @@ -12,6 +12,7 @@ import com.alibaba.excel.exception.ExcelDataConvertException; * @author Jiaju Zhuang **/ public class DateUtils { + public static final String DATE_FORMAT_10 = "yyyy-MM-dd"; public static final String DATE_FORMAT_14 = "yyyyMMddHHmmss"; public static final String DATE_FORMAT_17 = "yyyyMMdd HH:mm:ss"; public static final String DATE_FORMAT_19 = "yyyy-MM-dd HH:mm:ss"; @@ -65,8 +66,10 @@ public class DateUtils { return DATE_FORMAT_17; case 14: return DATE_FORMAT_14; + case 10: + return DATE_FORMAT_10; default: - throw new ExcelDataConvertException("can not find date format for:" + dateString); + throw new IllegalArgumentException("can not find date format for:" + dateString); } } diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java index 8a7d57da..4ef3b42a 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java @@ -71,11 +71,14 @@ public interface ExcelBuilder { /** * Close io + * + * @param onException */ - void finish(); + void finish(boolean onException); /** * add password + * * @param data * @param writeSheet * @param writeTable diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java index fc017fdd..9658f553 100644 --- a/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java +++ b/src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java @@ -31,10 +31,10 @@ public class ExcelBuilderImpl implements ExcelBuilder { FileUtils.createPoiFilesDirectory(); context = new WriteContextImpl(writeWorkbook); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } @@ -57,10 +57,10 @@ public class ExcelBuilderImpl implements ExcelBuilder { } excelWriteAddExecutor.add(data); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } @@ -80,18 +80,22 @@ public class ExcelBuilderImpl implements ExcelBuilder { } excelWriteFillExecutor.fill(data, fillConfig); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } + private void finishOnException() { + finish(true); + } + @Override - public void finish() { + public void finish(boolean onException) { if (context != null) { - context.finish(); + context.finish(onException); } } @@ -108,10 +112,10 @@ public class ExcelBuilderImpl implements ExcelBuilder { } excelWriteAddExecutor.add(data); } catch (RuntimeException e) { - finish(); + finishOnException(); throw e; } catch (Throwable e) { - finish(); + finishOnException(); throw new ExcelGenerateException(e); } } diff --git a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java index 8af1e62c..f0d37ede 100644 --- a/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java +++ b/src/main/java/com/alibaba/excel/write/builder/ExcelWriterBuilder.java @@ -146,6 +146,14 @@ public class ExcelWriterBuilder { return this; } + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + public ExcelWriterBuilder writeExcelOnException(Boolean writeExcelOnException) { + writeWorkbook.setWriteExcelOnException(writeExcelOnException); + return this; + } + /** * The default is all excel objects.if true , you can use {@link com.alibaba.excel.annotation.ExcelIgnore} ignore a * field. if false , you must use {@link com.alibaba.excel.annotation.ExcelProperty} to use a filed. diff --git a/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java b/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java index a1746299..1f09d47e 100644 --- a/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/AbstractExcelWriteExecutor.java @@ -59,8 +59,9 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { case EMPTY: return cellData; default: - throw new ExcelDataConvertException("Not supported data:" + value + " return type:" + cell.getCellType() - + "at row:" + cell.getRow().getRowNum()); + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), cellData, + excelContentProperty, "Not supported data:" + value + " return type:" + cell.getCellType() + + "at row:" + cell.getRow().getRowNum()); } } @@ -102,7 +103,8 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { converter = currentWriteHolder.converterMap().get(ConverterKeyBuild.buildKey(clazz)); } if (converter == null) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), + new CellData(CellDataTypeEnum.EMPTY), excelContentProperty, "Can not find 'Converter' support class " + clazz.getSimpleName() + "."); } CellData cellData; @@ -110,11 +112,13 @@ public abstract class AbstractExcelWriteExecutor implements ExcelWriteExecutor { cellData = converter.convertToExcelData(value, excelContentProperty, currentWriteHolder.globalConfiguration()); } catch (Exception e) { - throw new ExcelDataConvertException("Convert data:" + value + " error,at row:" + cell.getRow().getRowNum(), - e); + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), + new CellData(CellDataTypeEnum.EMPTY), excelContentProperty, + "Convert data:" + value + " error,at row:" + cell.getRow().getRowNum(), e); } if (cellData == null || cellData.getType() == null) { - throw new ExcelDataConvertException( + throw new ExcelDataConvertException(cell.getRow().getRowNum(), cell.getColumnIndex(), + new CellData(CellDataTypeEnum.EMPTY), excelContentProperty, "Convert data:" + value + " return null,at row:" + cell.getRow().getRowNum()); } return cellData; diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java index 0fa0cae2..5b0d8ae8 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java @@ -2,7 +2,6 @@ package com.alibaba.excel.write.executor; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -13,10 +12,10 @@ import org.apache.poi.ss.usermodel.Row; import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.enums.HeadKindEnum; -import com.alibaba.excel.metadata.BaseRowModel; import com.alibaba.excel.metadata.CellData; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.property.ExcelContentProperty; +import com.alibaba.excel.util.ClassUtils; import com.alibaba.excel.util.CollectionUtils; import com.alibaba.excel.util.WorkBookUtil; import com.alibaba.excel.util.WriteHandlerUtils; @@ -158,13 +157,11 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { continue; } Object value = beanMap.get(filedName); - if (value == null) { - continue; - } WriteHandlerUtils.beforeCellCreate(writeContext, row, null, cellIndex, relativeRowIndex, Boolean.FALSE); Cell cell = WorkBookUtil.createCell(row, cellIndex++); WriteHandlerUtils.afterCellCreate(writeContext, cell, null, relativeRowIndex, Boolean.FALSE); - CellData cellData = converterAndSet(currentWriteHolder, value.getClass(), cell, value, null); + CellData cellData = + converterAndSet(currentWriteHolder, value == null ? null : value.getClass(), cell, value, null); WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, null, relativeRowIndex, Boolean.FALSE); } } @@ -173,13 +170,8 @@ public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { if (!fieldList.isEmpty()) { return; } - Class tempClass = clazz; - while (tempClass != null) { - if (tempClass != BaseRowModel.class) { - Collections.addAll(fieldList, tempClass.getDeclaredFields()); - } - tempClass = tempClass.getSuperclass(); - } + ClassUtils.declaredFields(clazz, fieldList, + writeContext.writeWorkbookHolder().getWriteWorkbook().getConvertAllFiled()); } } diff --git a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java index d7cb6307..800b7c07 100644 --- a/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java +++ b/src/main/java/com/alibaba/excel/write/executor/ExcelWriteFillExecutor.java @@ -3,9 +3,11 @@ package com.alibaba.excel.write.executor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; @@ -57,6 +59,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { */ private Map> collectionFieldStyleCache = new HashMap>(8); + /** + * Row height cache for collection + */ + private Map collectionRowHeightCache = new HashMap(8); /** * Last index cache for collection fields */ @@ -121,7 +127,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (collectionLastIndexMap == null) { number--; } - sheet.shiftRows(maxRowIndex + 1, lastRowIndex, number); + sheet.shiftRows(maxRowIndex + 1, lastRowIndex, number, true, false); for (AnalysisCell analysisCell : templateAnalysisCache.get(writeContext.writeSheetHolder().getSheetNo())) { if (analysisCell.getRowIndex() > maxRowIndex) { analysisCell.setRowIndex(analysisCell.getRowIndex() + number); @@ -245,7 +251,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } else { row = sheet.createRow(lastRowIndex); } + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row, sheetNo); WriteHandlerUtils.afterRowCreate(writeContext, row, null, Boolean.FALSE); + } else { + checkRowHeight(analysisCell, fillConfig, isOriginalCell, row, sheetNo); } } Cell cell = row.getCell(lastColumnIndex); @@ -271,6 +280,21 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { return cell; } + private void checkRowHeight(AnalysisCell analysisCell, FillConfig fillConfig, boolean isOriginalCell, Row row, + Integer sheetNo) { + if (!analysisCell.getFirstColumn() || !WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection())) { + return; + } + if (isOriginalCell) { + collectionRowHeightCache.put(sheetNo, row.getHeight()); + return; + } + Short rowHeight = collectionRowHeightCache.get(sheetNo); + if (rowHeight != null) { + row.setHeight(rowHeight); + } + } + private List readTemplateData(Map> analysisCache) { Integer sheetNo = writeContext.writeSheetHolder().getSheetNo(); List analysisCellList = analysisCache.get(sheetNo); @@ -280,6 +304,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { Sheet sheet = writeContext.writeSheetHolder().getCachedSheet(); analysisCellList = new ArrayList(); List collectionAnalysisCellList = new ArrayList(); + Set firstColumnCache = new HashSet(); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) { @@ -290,7 +315,8 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (cell == null) { continue; } - String preparedData = prepareData(cell, analysisCellList, collectionAnalysisCellList, i, j); + String preparedData = + prepareData(cell, analysisCellList, collectionAnalysisCellList, i, j, firstColumnCache); // Prevent empty data from not being replaced if (preparedData != null) { cell.setCellValue(preparedData); @@ -310,10 +336,11 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { * @param collectionAnalysisCellList * @param rowIndex * @param columnIndex + * @param firstColumnCache * @return Returns the data that the cell needs to replace */ private String prepareData(Cell cell, List analysisCellList, - List collectionAnalysisCellList, int rowIndex, int columnIndex) { + List collectionAnalysisCellList, int rowIndex, int columnIndex, Set firstColumnCache) { if (!CellType.STRING.equals(cell.getCellTypeEnum())) { return null; } @@ -323,6 +350,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } StringBuilder preparedData = new StringBuilder(); AnalysisCell analysisCell = null; + int startIndex = 0; int length = value.length(); int lastPrepareDataIndex = 0; @@ -376,6 +404,13 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { } lastPrepareDataIndex = suffixIndex + 1; } + return dealAnalysisCell(analysisCell, value, rowIndex, lastPrepareDataIndex, length, analysisCellList, + collectionAnalysisCellList, firstColumnCache, preparedData); + } + + private String dealAnalysisCell(AnalysisCell analysisCell, String value, int rowIndex, int lastPrepareDataIndex, + int length, List analysisCellList, List collectionAnalysisCellList, + Set firstColumnCache, StringBuilder preparedData) { if (analysisCell != null) { if (lastPrepareDataIndex == length) { analysisCell.getPrepareDataList().add(StringUtils.EMPTY); @@ -386,6 +421,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { if (WriteTemplateAnalysisCellTypeEnum.COMMON.equals(analysisCell.getCellType())) { analysisCellList.add(analysisCell); } else { + if (!firstColumnCache.contains(rowIndex)) { + analysisCell.setFirstColumn(Boolean.TRUE); + firstColumnCache.add(rowIndex); + } collectionAnalysisCellList.add(analysisCell); } return preparedData.toString(); @@ -403,6 +442,7 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor { List prepareDataList = new ArrayList(); analysisCell.setPrepareDataList(prepareDataList); analysisCell.setCellType(WriteTemplateAnalysisCellTypeEnum.COMMON); + analysisCell.setFirstColumn(Boolean.FALSE); return analysisCell; } diff --git a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java index 85694a81..b0878047 100644 --- a/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java +++ b/src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java @@ -13,24 +13,39 @@ import com.alibaba.excel.metadata.Head; */ public class LoopMergeStrategy extends AbstractMergeStrategy { private int eachRow; + private int columnCount; private int columnIndex; public LoopMergeStrategy(int eachRow, int columnIndex) { + this(eachRow, 1, columnIndex); + } + + public LoopMergeStrategy(int eachRow, int columnCount, int columnIndex) { if (eachRow < 1) { throw new IllegalArgumentException("EachRows must be greater than 1"); } + if (columnCount < 1) { + throw new IllegalArgumentException("ColumnCount must be greater than 1"); + } + if (columnCount == 1 && eachRow == 1) { + throw new IllegalArgumentException("ColumnCount or eachRows must be greater than 1"); + } if (columnIndex < 0) { throw new IllegalArgumentException("ColumnIndex must be greater than 0"); } this.eachRow = eachRow; + this.columnCount = columnCount; this.columnIndex = columnIndex; } @Override protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) { if (head.getColumnIndex() == columnIndex && relativeRowIndex % eachRow == 0) { - CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(), - cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex()); + CellRangeAddress cellRangeAddress = new CellRangeAddress( + cell.getRowIndex(), + cell.getRowIndex() + eachRow - 1, + cell.getColumnIndex(), + cell.getColumnIndex() + columnCount - 1); sheet.addMergedRegion(cellRangeAddress); } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java index 738299af..c28e0412 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java +++ b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java @@ -63,6 +63,10 @@ public class WriteWorkbook extends WriteBasicParameter { * Comment and RichTextString are only supported in memory mode. */ private Boolean inMemory; + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + private Boolean writeExcelOnException; /** * The default is all excel objects.Default is true. *

@@ -169,4 +173,12 @@ public class WriteWorkbook extends WriteBasicParameter { public void setInMemory(Boolean inMemory) { this.inMemory = inMemory; } + + public Boolean getWriteExcelOnException() { + return writeExcelOnException; + } + + public void setWriteExcelOnException(Boolean writeExcelOnException) { + this.writeExcelOnException = writeExcelOnException; + } } diff --git a/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java b/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java index 791e2643..47a25947 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java +++ b/src/main/java/com/alibaba/excel/write/metadata/fill/AnalysisCell.java @@ -16,6 +16,7 @@ public class AnalysisCell { private List prepareDataList; private Boolean onlyOneVariable; private WriteTemplateAnalysisCellTypeEnum cellType; + private Boolean firstColumn; public int getColumnIndex() { return columnIndex; @@ -65,6 +66,14 @@ public class AnalysisCell { this.cellType = cellType; } + public Boolean getFirstColumn() { + return firstColumn; + } + + public void setFirstColumn(Boolean firstColumn) { + this.firstColumn = firstColumn; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index 4de6ade6..c8f7555f 100644 --- a/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java +++ b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java @@ -103,6 +103,10 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { * Comment and RichTextString are only supported in memory mode. */ private Boolean inMemory; + /** + * Excel is also written in the event of an exception being thrown.The default false. + */ + private Boolean writeExcelOnException; public WriteWorkbookHolder(WriteWorkbook writeWorkbook) { super(writeWorkbook, null, writeWorkbook.getConvertAllFiled()); @@ -128,7 +132,10 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { throw new ExcelGenerateException("Copy template failure.", e); } if (writeWorkbook.getExcelType() == null) { - if (file != null && file.getName().endsWith(ExcelTypeEnum.XLS.getValue())) { + boolean isXls = (file != null && file.getName().endsWith(ExcelTypeEnum.XLS.getValue())) + || (writeWorkbook.getTemplateFile() != null + && writeWorkbook.getTemplateFile().getName().endsWith(ExcelTypeEnum.XLS.getValue())); + if (isXls) { this.excelType = ExcelTypeEnum.XLS; } else { this.excelType = ExcelTypeEnum.XLSX; @@ -148,6 +155,11 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { } else { this.inMemory = writeWorkbook.getInMemory(); } + if (writeWorkbook.getWriteExcelOnException() == null) { + this.writeExcelOnException = Boolean.FALSE; + } else { + this.writeExcelOnException = writeWorkbook.getWriteExcelOnException(); + } } private void copyTemplate() throws IOException { @@ -281,6 +293,14 @@ public class WriteWorkbookHolder extends AbstractWriteHolder { this.inMemory = inMemory; } + public Boolean getWriteExcelOnException() { + return writeExcelOnException; + } + + public void setWriteExcelOnException(Boolean writeExcelOnException) { + this.writeExcelOnException = writeExcelOnException; + } + @Override public HolderEnum holderType() { return HolderEnum.WORKBOOK; diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java index 0159cbc7..32a01852 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java @@ -19,12 +19,15 @@ import com.alibaba.excel.write.metadata.fill.FillConfig; /** * 写的填充写法 * + * @since 2.1.1 * @author Jiaju Zhuang */ @Ignore public class FillTest { /** * 最简单的填充 + * + * @since 2.1.1 */ @Test public void simpleFill() { @@ -51,6 +54,8 @@ public class FillTest { /** * 填充列表 + * + * @since 2.1.1 */ @Test public void listFill() { @@ -76,6 +81,8 @@ public class FillTest { /** * 复杂的填充 + * + * @since 2.1.1 */ @Test public void complexFill() { @@ -105,6 +112,8 @@ public class FillTest { * 数据量大的复杂填充 *

* 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 */ @Test public void complexFillWithTable() { @@ -145,6 +154,8 @@ public class FillTest { /** * 横向的填充 + * + * @since 2.1.1 */ @Test public void horizontalFill() { diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java new file mode 100644 index 00000000..8ca6b026 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDAO.java @@ -0,0 +1,15 @@ +package com.alibaba.easyexcel.test.demo.read; + +import java.util.List; + +/** + * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 + * + * @author Jiaju Zhuang + **/ +public class DemoDAO { + + public void save(List list) { + // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java index 434af3b6..f26ff4be 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java @@ -23,19 +23,52 @@ public class DemoDataListener extends AnalysisEventListener { */ private static final int BATCH_COUNT = 5; List list = new ArrayList(); + /** + * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 + */ + private DemoDAO demoDAO; + + public DemoDataListener() { + // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 + demoDAO = new DemoDAO(); + } + + /** + * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 + * + * @param demoDAO + */ + public DemoDataListener(DemoDAO demoDAO) { + this.demoDAO = demoDAO; + } + /** + * 这个每一条数据解析都会来调用 + * + * @param data + * one row value. Is is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ @Override public void invoke(DemoData data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); + // 存储完成清理 list list.clear(); } } + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ @Override public void doAfterAllAnalysed(AnalysisContext context) { + // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); LOGGER.info("所有数据解析完成!"); } @@ -45,6 +78,7 @@ public class DemoDataListener extends AnalysisEventListener { */ private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); + demoDAO.save(list); LOGGER.info("存储数据库成功!"); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java index 0f00719d..e4f9571a 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/DemoExceptionListener.java @@ -39,8 +39,8 @@ public class DemoExceptionListener extends AnalysisEventListener { LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; - LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(), - excelDataConvertException.getColumnIndex()); + LOGGER.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), + excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData()); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 9ebafa4f..0dc22aa7 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java @@ -202,17 +202,15 @@ public class ReadTest { public void synchronousRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish - List list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); - for (Object obj : list) { - DemoData data = (DemoData)obj; + List list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); + for (DemoData data : list) { LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish - list = EasyExcel.read(fileName).sheet().doReadSync(); - for (Object obj : list) { + List> listMap = EasyExcel.read(fileName).sheet().doReadSync(); + for (Map data : listMap) { // 返回每条数据的键值对 表示所在的列 和所在列的值 - Map data = (Map)obj; LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java new file mode 100644 index 00000000..eac96625 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDAO.java @@ -0,0 +1,20 @@ +package com.alibaba.easyexcel.test.demo.web; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.alibaba.easyexcel.test.demo.read.DemoData; + +/** + * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 + * + * @author Jiaju Zhuang + **/ +@Repository +public class UploadDAO { + + public void save(List list) { + // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java index 7475ced3..e61a5d65 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/UploadDataListener.java @@ -15,26 +15,61 @@ import com.alibaba.fastjson.JSON; * * @author Jiaju Zhuang */ +// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 public class UploadDataListener extends AnalysisEventListener { - private static final Logger LOGGER = LoggerFactory.getLogger(UploadDataListener.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(com.alibaba.easyexcel.test.demo.read.DemoDataListener.class); /** * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 5; List list = new ArrayList(); + /** + * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 + */ + private UploadDAO uploadDAO; + + public UploadDataListener() { + // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数 + uploadDAO = new UploadDAO(); + } + + /** + * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 + * + * @param uploadDAO + */ + public UploadDataListener(UploadDAO uploadDAO) { + this.uploadDAO = uploadDAO; + } + /** + * 这个每一条数据解析都会来调用 + * + * @param data + * one row value. Is is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ @Override public void invoke(UploadData data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); + // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); + // 存储完成清理 list list.clear(); } } + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ @Override public void doAfterAllAnalysed(AnalysisContext context) { + // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); LOGGER.info("所有数据解析完成!"); } @@ -44,6 +79,7 @@ public class UploadDataListener extends AnalysisEventListener { */ private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); + uploadDAO.save(list); LOGGER.info("存储数据库成功!"); } } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java index cc525629..98f400a4 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java @@ -2,14 +2,15 @@ package com.alibaba.easyexcel.test.demo.web; import java.io.IOException; import java.net.URLEncoder; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import com.alibaba.excel.EasyExcel; +import com.alibaba.fastjson.JSON; /** * web读写案例 @@ -25,8 +27,12 @@ import com.alibaba.excel.EasyExcel; **/ @Controller public class WebTest { + + @Autowired + private UploadDAO uploadDAO; + /** - * 文件下载 + * 文件下载(失败了会返回一个有部分数据的Excel) *

* 1. 创建excel对应的实体对象 参照{@link DownloadData} *

@@ -45,6 +51,35 @@ public class WebTest { EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); } + /** + * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel) + * + * @since 2.1.1 + */ + @GetMapping("downloadFailedUsingJson") + public void downloadFailedUsingJson(HttpServletResponse response) throws IOException { + // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman + try { + response.setContentType("application/vnd.ms-excel"); + response.setCharacterEncoding("utf-8"); + // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 + String fileName = URLEncoder.encode("测试", "UTF-8"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + // 这里需要设置不关闭流 + EasyExcel.write(response.getOutputStream(), DownloadData.class).autoCloseStream(Boolean.FALSE).sheet("模板") + .doWrite(data()); + } catch (Exception e) { + // 重置response + response.reset(); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + Map map = new HashMap(); + map.put("status", "failure"); + map.put("message", "下载文件失败" + e.getMessage()); + response.getWriter().println(JSON.toJSONString(map)); + } + } + /** * 文件上传 *

@@ -57,7 +92,7 @@ public class WebTest { @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { - EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead(); + EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java index 951026cf..3fcb9484 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/ImageData.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.demo.write; import java.io.File; import java.io.InputStream; +import java.net.URL; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; @@ -27,4 +28,10 @@ public class ImageData { @ExcelProperty(converter = StringImageConverter.class) private String string; private byte[] byteArray; + /** + * 根据url导出 + * + * @since 2.1.1 + */ + private URL url; } diff --git a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java index f9a69db7..3e27bd38 100644 --- a/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java @@ -2,6 +2,7 @@ package com.alibaba.easyexcel.test.demo.write; import java.io.File; import java.io.InputStream; +import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; @@ -215,12 +216,14 @@ public class WriteTest { ImageData imageData = new ImageData(); list.add(imageData); String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"; - // 放入四种类型的图片 实际使用只要选一种即可 + // 放入五种类型的图片 实际使用只要选一种即可 imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath))); imageData.setFile(new File(imagePath)); imageData.setString(imagePath); inputStream = FileUtils.openInputStream(new File(imagePath)); imageData.setInputStream(inputStream); + imageData.setUrl(new URL( + "https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg")); EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list); } finally { if (inputStream != null) { diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java new file mode 100644 index 00000000..6c82082d --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/FillTempTest.java @@ -0,0 +1,110 @@ +package com.alibaba.easyexcel.test.temp; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Ignore; +import org.junit.Test; + +import com.alibaba.easyexcel.test.demo.fill.FillData; +import com.alibaba.easyexcel.test.util.TestFileUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillConfig; +import com.alibaba.excel.write.style.row.SimpleRowHeightStyleStrategy; + +/** + * 写的填充写法 + * + * @since 2.1.1 + * @author Jiaju Zhuang + */ +@Ignore +public class FillTempTest { + + /** + * 复杂的填充 + * + * @since 2.1.1 + */ + @Test + public void complexFill() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + String templateFileName = "D:\\test\\complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。 + // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用 + // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存 + // 如果数据量大 list不是最后一行 参照下一个 + FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); + excelWriter.fill(data(), fillConfig, writeSheet); + excelWriter.fill(data(), fillConfig, writeSheet); + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + map.put("total", 1000); + excelWriter.fill(map, writeSheet); + excelWriter.finish(); + } + + /** + * 数据量大的复杂填充 + *

+ * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 + * + * @since 2.1.1 + */ + @Test + public void complexFillWithTable() { + // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 + // {} 代表普通变量 {.} 代表是list的变量 + // 这里模板 删除了list以后的数据,也就是统计的这一行 + String templateFileName = "D:\\test\\complex.xlsx"; + + String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; + ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build(); + WriteSheet writeSheet = EasyExcel.writerSheet().build(); + // 直接写入数据 + excelWriter.fill(data(), writeSheet); + excelWriter.fill(data(), writeSheet); + + // 写入list之前的数据 + Map map = new HashMap(); + map.put("date", "2019年10月9日13:28:28"); + excelWriter.fill(map, writeSheet); + + // list 后面还有个统计 想办法手动写入 + // 这里偷懒直接用list 也可以用对象 + List> totalListList = new ArrayList>(); + List totalList = new ArrayList(); + totalListList.add(totalList); + totalList.add(null); + totalList.add(null); + totalList.add(null); + // 第四列 + totalList.add("统计:1000"); + // 这里是write 别和fill 搞错了 + excelWriter.write(totalListList, writeSheet); + excelWriter.finish(); + // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 + // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 + } + + private List data() { + List list = new ArrayList(); + for (int i = 0; i < 10; i++) { + FillData fillData = new FillData(); + list.add(fillData); + fillData.setName("张三"); + fillData.setNumber(5.2); + } + return list; + } +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java index 367ee583..f7778338 100644 --- a/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java +++ b/src/test/java/com/alibaba/easyexcel/test/temp/poi/PoiTest.java @@ -79,11 +79,11 @@ public class PoiTest { @Test public void lastRowNum255() throws IOException, InvalidFormatException { - String file = TestFileUtil.getPath() + "fill" + File.separator + "complex.xlsx"; + String file = "D:\\test\\complex.xlsx"; XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new File(file)); SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook); Sheet xssfSheet = xssfWorkbook.getSheetAt(0); - xssfSheet.shiftRows(2, 4, 10); + xssfSheet.shiftRows(1, 4, 10, true, true); FileOutputStream fileout = new FileOutputStream("d://test/r2" + System.currentTimeMillis() + ".xlsx"); sxssfWorkbook.write(fileout); diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java new file mode 100644 index 00000000..e52dc51a --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java @@ -0,0 +1,43 @@ +package com.alibaba.easyexcel.test.temp.read; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.event.AnalysisEventListener; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.fastjson.JSON; + +/** + * 模板的读取类 + * + * @author Jiaju Zhuang + */ +public class HDListener extends AnalysisEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(HDListener.class); + /** + * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 + */ + private static final int BATCH_COUNT = 5; + + @Override + public void invokeHeadMap(Map headMap, AnalysisContext context) { + LOGGER.info("HEAD:{}", JSON.toJSONString(headMap)); + LOGGER.info("total:{}", context.readSheetHolder().getTotal()); + + } + + @Override + public void invoke(HeadReadData data, AnalysisContext context) { + LOGGER.info("index:{}", context.readRowHolder().getRowIndex()); + LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + LOGGER.info("所有数据解析完成!"); + } + +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java new file mode 100644 index 00000000..538e2663 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java @@ -0,0 +1,20 @@ +package com.alibaba.easyexcel.test.temp.read; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Data +@Accessors(chain = true) +public class HeadReadData { + @ExcelProperty("头1") + private String h1; + @ExcelProperty({"头", "头2"}) + private String h2; +} diff --git a/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java new file mode 100644 index 00000000..5127f72f --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java @@ -0,0 +1,28 @@ +package com.alibaba.easyexcel.test.temp.read; + +import java.io.File; + +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.EasyExcel; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Ignore +public class HeadReadTest { + private static final Logger LOGGER = LoggerFactory.getLogger(HeadReadTest.class); + + @Test + public void test() throws Exception { + File file = new File("D:\\test\\headt1.xls"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).sheet(0).doRead(); + + } + +} diff --git a/update.md b/update.md index 36b23811..9bd246e5 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,18 @@ +# 2.1.1 +* 发布正式版 +* 修改map返回为LinkedHashMap +* 修改同步读取返回对象支持泛型 +* 修复03版不能直接读取第二个sheet的bug [Issue #772](https://github.com/alibaba/easyexcel/issues/772) +* 新增支持图片导出用URL [Issue #774](https://github.com/alibaba/easyexcel/issues/774) +* 加入多次关闭判断,防止多次关闭异常 +* 加入根据模板自动识别导出的excel类型 +* 修改默认失败后,不再往文件流写入数据。通过参数`writeExcelOnException` 参数设置异常了也要写入前面的数据。 +* 循环合并策略支持一次性合并多列 +* `ExcelDataConvertException`返回新增具体报错的数据 +* 加入解析class缓存 +* 修复填充的时候行高不复制的Bug [Issue #780](https://github.com/alibaba/easyexcel/issues/780) +* 修复03版无法获取大概总行数的bug + # 2.1.0-beta4 * 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747) * 修改afterRowDispose错误 [Issue #751](https://github.com/alibaba/easyexcel/issues/751)