diff --git a/README.md b/README.md index fd5c5a2..1251ebd 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/src/main/java/com/alibaba/excel/ExcelReader.java b/src/main/java/com/alibaba/excel/ExcelReader.java index 0887e32..07a373b 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 65a99fe..a0b4795 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 4002b84..f3ef950 100644 --- a/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java +++ b/src/main/java/com/alibaba/excel/analysis/ExcelAnalyserImpl.java @@ -34,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 { @@ -121,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 a489cce..76a869a 100644 --- a/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java +++ b/src/main/java/com/alibaba/excel/analysis/v03/XlsSaxAnalyser.java @@ -121,7 +121,7 @@ public class XlsSaxAnalyser implements HSSFListener, ExcelReadExecutor { private void init() { lastRowNumber = 0; lastColumnNumber = 0; - records = new TreeMap(); + records = new LinkedHashMap(); buildXlsRecordHandlers(); } 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 3a86294..083edbc 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/v07/handlers/DefaultCellHandler.java b/src/main/java/com/alibaba/excel/analysis/v07/handlers/DefaultCellHandler.java index 002999d..e0b9c41 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 @@ -12,7 +12,6 @@ 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; @@ -55,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 c6e85c9..0a59e1d 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 ec49ea3..3f2b51a 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 e53c1f8..77e8b59 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 0000000..b622d66 --- /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/read/builder/ExcelReaderSheetBuilder.java b/src/main/java/com/alibaba/excel/read/builder/ExcelReaderSheetBuilder.java index af3b05e..d494e23 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/metadata/holder/AbstractReadHolder.java b/src/main/java/com/alibaba/excel/read/metadata/holder/AbstractReadHolder.java index 157f90e..786d438 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 @@ -208,6 +208,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/util/ConverterUtils.java b/src/main/java/com/alibaba/excel/util/ConverterUtils.java index b6244c2..200440e 100644 --- a/src/main/java/com/alibaba/excel/util/ConverterUtils.java +++ b/src/main/java/com/alibaba/excel/util/ConverterUtils.java @@ -33,10 +33,17 @@ public class ConverterUtils { */ public static Map convertToStringMap(Map cellDataMap, ReadHolder readHolder) { Map stringMap = new HashMap(cellDataMap.size() * 4 / 3 + 1); + 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 = @@ -46,7 +53,7 @@ public class ConverterUtils { "Converter not found, convert " + cellData.getType() + " to String"); } try { - stringMap.put(entry.getKey(), + stringMap.put(key, (String)(converter.convertToJavaData(cellData, null, readHolder.globalConfiguration()))); } catch (Exception e) { throw new ExcelDataConvertException("Convert data " + cellData + " to String 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 1ea05c0..0df5cf4 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,6 +66,8 @@ 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); } diff --git a/src/main/java/com/alibaba/excel/write/ExcelBuilder.java b/src/main/java/com/alibaba/excel/write/ExcelBuilder.java index 8a7d57d..4ef3b42 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 fc017fd..9658f55 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 8af1e62..f0d37ed 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/metadata/WriteWorkbook.java b/src/main/java/com/alibaba/excel/write/metadata/WriteWorkbook.java index 738299a..c28e041 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/holder/WriteWorkbookHolder.java b/src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java index 4de6ade..c8f7555 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 0159cbc..32a0185 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 0000000..8ca6b02 --- /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 434af3b..f26ff4b 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/ReadTest.java b/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java index 9ebafa4..0dc22aa 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 0000000..eac9662 --- /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 7475ced..e61a5d6 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 cc52562..98f400a 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 951026c..3fcb948 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 f9a69db..3e27bd3 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/read/HDListener.java b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java new file mode 100644 index 0000000..196b2da --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HDListener.java @@ -0,0 +1,42 @@ +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)); + + } + + @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 0000000..38ee661 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadData.java @@ -0,0 +1,18 @@ +package com.alibaba.easyexcel.test.temp.read; + +import com.alibaba.excel.annotation.ExcelProperty; + +import lombok.Data; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +@Data +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 0000000..f356524 --- /dev/null +++ b/src/test/java/com/alibaba/easyexcel/test/temp/read/HeadReadTest.java @@ -0,0 +1,25 @@ +package com.alibaba.easyexcel.test.temp.read; + +import java.io.File; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.excel.EasyExcel; + +/** + * 临时测试 + * + * @author Jiaju Zhuang + **/ +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.xlsx"); + EasyExcel.read(file, HeadReadData.class, new HDListener()).sheet().doRead(); + } + +} diff --git a/update.md b/update.md index edc64f4..4055d43 100644 --- a/update.md +++ b/update.md @@ -1,5 +1,12 @@ -# 2.1.0-bea5 +# 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` 参数设置异常了也要写入前面的数据。 # 2.1.0-beta4 * 修改最长匹配策略会空指针的bug [Issue #747](https://github.com/alibaba/easyexcel/issues/747)