diff --git a/pigx-common/pigx-common-excel/pom.xml b/pigx-common/pigx-common-excel/pom.xml new file mode 100644 index 000000000..dbac17b2b --- /dev/null +++ b/pigx-common/pigx-common-excel/pom.xml @@ -0,0 +1,97 @@ + + + + + 4.0.0 + + com.pig4cloud + pigx-common + 4.1.0 + + + pigx-common-excel + jar + + pigx excel 操作 + + + 4.1.2 + 2.2.10 + + + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-aop + + + com.alibaba + easyexcel + ${easypoi.version} + + + org.apache.poi + poi + + + org.apache.poi + poi-ooxml + + + org.apache.poi + poi-ooxml-schemas + + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + jakarta.servlet + jakarta.servlet-api + + + diff --git a/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/context/WriteContextImpl.java b/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/context/WriteContextImpl.java new file mode 100644 index 000000000..faed7c2d8 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/context/WriteContextImpl.java @@ -0,0 +1,526 @@ +package com.alibaba.excel.context; + +import com.alibaba.excel.enums.WriteTypeEnum; +import com.alibaba.excel.exception.ExcelGenerateException; +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.support.ExcelTypeEnum; +import com.alibaba.excel.util.*; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.WriteTable; +import com.alibaba.excel.write.metadata.WriteWorkbook; +import com.alibaba.excel.write.metadata.holder.WriteHolder; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import com.alibaba.excel.write.property.ExcelWriteHeadProperty; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackageAccess; +import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.EncryptionMode; +import org.apache.poi.poifs.crypt.Encryptor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Map; +import java.util.UUID; + +/** + * A context is the main anchorage point of a excel writer. + * + * @author jipengfei + */ +@Slf4j +public class WriteContextImpl implements WriteContext { + + static { + log.debug("======[WriteContextImpl] 修复版被加载了======="); + } + + private static final Logger LOGGER = LoggerFactory.getLogger(WriteContextImpl.class); + + /** + * The Workbook currently written + */ + private WriteWorkbookHolder writeWorkbookHolder; + + /** + * Current sheet holder + */ + private WriteSheetHolder writeSheetHolder; + + /** + * The table currently written + */ + private WriteTableHolder writeTableHolder; + + /** + * Configuration of currently operated cell + */ + private WriteHolder currentWriteHolder; + + /** + * Prevent multiple shutdowns + */ + private boolean finished = false; + + public WriteContextImpl(WriteWorkbook writeWorkbook) { + if (writeWorkbook == null) { + throw new IllegalArgumentException("Workbook argument cannot be null"); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Begin to Initialization 'WriteContextImpl'"); + } + initCurrentWorkbookHolder(writeWorkbook); + WriteHandlerUtils.beforeWorkbookCreate(this); + try { + WorkBookUtil.createWorkBook(writeWorkbookHolder); + } + catch (Exception e) { + throw new ExcelGenerateException("Create workbook failure", e); + } + WriteHandlerUtils.afterWorkbookCreate(this); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Initialization 'WriteContextImpl' complete"); + } + } + + private void initCurrentWorkbookHolder(WriteWorkbook writeWorkbook) { + writeWorkbookHolder = new WriteWorkbookHolder(writeWorkbook); + currentWriteHolder = writeWorkbookHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeWorkbookHolder"); + } + } + + /** + * @param writeSheet + */ + @Override + public void currentSheet(WriteSheet writeSheet, WriteTypeEnum writeType) { + if (writeSheet == null) { + throw new IllegalArgumentException("Sheet argument cannot be null"); + } + if (selectSheetFromCache(writeSheet)) { + return; + } + + initCurrentSheetHolder(writeSheet); + + // Workbook handler need to supplementary execution + WriteHandlerUtils.beforeWorkbookCreate(this, true); + WriteHandlerUtils.afterWorkbookCreate(this, true); + + // Initialization current sheet + initSheet(writeType); + } + + private boolean selectSheetFromCache(WriteSheet writeSheet) { + writeSheetHolder = null; + Integer sheetNo = writeSheet.getSheetNo(); + if (sheetNo == null && StringUtils.isEmpty(writeSheet.getSheetName())) { + sheetNo = 0; + } + if (sheetNo != null) { + writeSheetHolder = writeWorkbookHolder.getHasBeenInitializedSheetIndexMap().get(sheetNo); + } + if (writeSheetHolder == null && !StringUtils.isEmpty(writeSheet.getSheetName())) { + writeSheetHolder = writeWorkbookHolder.getHasBeenInitializedSheetNameMap().get(writeSheet.getSheetName()); + } + if (writeSheetHolder == null) { + return false; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Sheet:{} is already existed", writeSheet.getSheetNo()); + } + writeSheetHolder.setNewInitialization(Boolean.FALSE); + writeTableHolder = null; + currentWriteHolder = writeSheetHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeSheetHolder"); + } + return true; + } + + private void initCurrentSheetHolder(WriteSheet writeSheet) { + writeSheetHolder = new WriteSheetHolder(writeSheet, writeWorkbookHolder); + writeTableHolder = null; + currentWriteHolder = writeSheetHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeSheetHolder"); + } + } + + private void initSheet(WriteTypeEnum writeType) { + WriteHandlerUtils.beforeSheetCreate(this); + Sheet currentSheet; + try { + if (writeSheetHolder.getSheetNo() != null) { + // When the add default sort order of appearance + if (WriteTypeEnum.ADD.equals(writeType) && writeWorkbookHolder.getTempTemplateInputStream() == null) { + currentSheet = createSheet(); + } + else { + currentSheet = writeWorkbookHolder.getWorkbook().getSheetAt(writeSheetHolder.getSheetNo()); + writeSheetHolder.setCachedSheet( + writeWorkbookHolder.getCachedWorkbook().getSheetAt(writeSheetHolder.getSheetNo())); + } + } + else { + // sheet name must not null + currentSheet = writeWorkbookHolder.getWorkbook().getSheet(writeSheetHolder.getSheetName()); + writeSheetHolder.setCachedSheet( + writeWorkbookHolder.getCachedWorkbook().getSheet(writeSheetHolder.getSheetName())); + } + } + catch (Exception e) { + currentSheet = createSheet(); + } + if (currentSheet == null) { + currentSheet = createSheet(); + } + writeSheetHolder.setSheet(currentSheet); + WriteHandlerUtils.afterSheetCreate(this); + if (WriteTypeEnum.ADD.equals(writeType)) { + // Initialization head + initHead(writeSheetHolder.excelWriteHeadProperty()); + } + writeWorkbookHolder.getHasBeenInitializedSheetIndexMap().put(writeSheetHolder.getSheetNo(), writeSheetHolder); + writeWorkbookHolder.getHasBeenInitializedSheetNameMap().put(writeSheetHolder.getSheetName(), writeSheetHolder); + } + + private Sheet createSheet() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Can not find sheet:{} ,now create it", writeSheetHolder.getSheetNo()); + } + if (StringUtils.isEmpty(writeSheetHolder.getSheetName())) { + writeSheetHolder.setSheetName(writeSheetHolder.getSheetNo().toString()); + } + Sheet currentSheet = WorkBookUtil.createSheet(writeWorkbookHolder.getWorkbook(), + writeSheetHolder.getSheetName()); + writeSheetHolder.setCachedSheet(currentSheet); + return currentSheet; + } + + public void initHead(ExcelWriteHeadProperty excelWriteHeadProperty) { + if (!currentWriteHolder.needHead() || !currentWriteHolder.excelWriteHeadProperty().hasHead()) { + return; + } + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); + newRowIndex += currentWriteHolder.relativeHeadRowIndex(); + // Combined head + if (currentWriteHolder.automaticMergeHead()) { + addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex); + } + for (int relativeRowIndex = 0, + i = newRowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + newRowIndex; i++, relativeRowIndex++) { + WriteHandlerUtils.beforeRowCreate(this, newRowIndex, relativeRowIndex, Boolean.TRUE); + Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i); + WriteHandlerUtils.afterRowCreate(this, row, relativeRowIndex, Boolean.TRUE); + addOneRowOfHeadDataToExcel(row, excelWriteHeadProperty.getHeadMap(), relativeRowIndex); + WriteHandlerUtils.afterRowDispose(this, row, relativeRowIndex, Boolean.TRUE); + } + } + + private void addMergedRegionToCurrentSheet(ExcelWriteHeadProperty excelWriteHeadProperty, int rowIndex) { + for (com.alibaba.excel.metadata.CellRange cellRangeModel : excelWriteHeadProperty.headCellRangeList()) { + writeSheetHolder.getSheet() + .addMergedRegionUnsafe(new CellRangeAddress(cellRangeModel.getFirstRow() + rowIndex, + cellRangeModel.getLastRow() + rowIndex, cellRangeModel.getFirstCol(), + cellRangeModel.getLastCol())); + } + } + + private void addOneRowOfHeadDataToExcel(Row row, Map headMap, int relativeRowIndex) { + for (Map.Entry entry : headMap.entrySet()) { + Head head = entry.getValue(); + int columnIndex = entry.getKey(); + WriteHandlerUtils.beforeCellCreate(this, row, head, columnIndex, relativeRowIndex, Boolean.TRUE); + Cell cell = row.createCell(columnIndex); + WriteHandlerUtils.afterCellCreate(this, cell, head, relativeRowIndex, Boolean.TRUE); + cell.setCellValue(head.getHeadNameList().get(relativeRowIndex)); + WriteHandlerUtils.afterCellDispose(this, (CellData) null, cell, head, relativeRowIndex, Boolean.TRUE); + } + } + + @Override + public void currentTable(WriteTable writeTable) { + if (writeTable == null) { + return; + } + if (writeTable.getTableNo() == null || writeTable.getTableNo() <= 0) { + writeTable.setTableNo(0); + } + if (writeSheetHolder.getHasBeenInitializedTable().containsKey(writeTable.getTableNo())) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Table:{} is already existed", writeTable.getTableNo()); + } + writeTableHolder = writeSheetHolder.getHasBeenInitializedTable().get(writeTable.getTableNo()); + writeTableHolder.setNewInitialization(Boolean.FALSE); + currentWriteHolder = writeTableHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeTableHolder"); + } + return; + } + + initCurrentTableHolder(writeTable); + + // Workbook and sheet handler need to supplementary execution + WriteHandlerUtils.beforeWorkbookCreate(this, true); + WriteHandlerUtils.afterWorkbookCreate(this, true); + WriteHandlerUtils.beforeSheetCreate(this, true); + WriteHandlerUtils.afterSheetCreate(this, true); + + initHead(writeTableHolder.excelWriteHeadProperty()); + } + + private void initCurrentTableHolder(WriteTable writeTable) { + writeTableHolder = new WriteTableHolder(writeTable, writeSheetHolder, writeWorkbookHolder); + writeSheetHolder.getHasBeenInitializedTable().put(writeTable.getTableNo(), writeTableHolder); + currentWriteHolder = writeTableHolder; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("CurrentConfiguration is writeTableHolder"); + } + } + + @Override + public WriteWorkbookHolder writeWorkbookHolder() { + return writeWorkbookHolder; + } + + @Override + public WriteSheetHolder writeSheetHolder() { + return writeSheetHolder; + } + + @Override + public WriteTableHolder writeTableHolder() { + return writeTableHolder; + } + + @Override + public WriteHolder currentWriteHolder() { + return currentWriteHolder; + } + + @Override + public void finish(boolean onException) { + if (finished) { + return; + } + finished = true; + WriteHandlerUtils.afterWorkbookDispose(this); + if (writeWorkbookHolder == null) { + return; + } + Throwable throwable = null; + boolean isOutputStreamEncrypt = false; + // 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 { + if (writeExcel) { + writeWorkbookHolder.getWorkbook().write(writeWorkbookHolder.getOutputStream()); + } + writeWorkbookHolder.getWorkbook().close(); + } + catch (Throwable t) { + throwable = t; + } + } + try { + Workbook workbook = writeWorkbookHolder.getWorkbook(); + if (workbook instanceof SXSSFWorkbook) { + ((SXSSFWorkbook) workbook).dispose(); + } + } + catch (Throwable t) { + throwable = t; + } + try { + if (writeWorkbookHolder.getAutoCloseStream() && writeWorkbookHolder.getOutputStream() != null) { + writeWorkbookHolder.getOutputStream().close(); + } + } + catch (Throwable t) { + throwable = t; + } + if (writeExcel && !isOutputStreamEncrypt) { + try { + doFileEncrypt07(); + } + catch (Throwable t) { + throwable = t; + } + } + try { + if (writeWorkbookHolder.getTempTemplateInputStream() != null) { + writeWorkbookHolder.getTempTemplateInputStream().close(); + } + } + catch (Throwable t) { + throwable = t; + } + clearEncrypt03(); + removeThreadLocalCache(); + if (throwable != null) { + throw new ExcelGenerateException("Can not close IO.", throwable); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Finished write."); + } + } + + private void removeThreadLocalCache() { + NumberDataFormatterUtils.removeThreadLocalCache(); + DateUtils.removeThreadLocalCache(); + } + + @Override + public Sheet getCurrentSheet() { + return writeSheetHolder.getSheet(); + } + + @Override + public boolean needHead() { + return writeSheetHolder.needHead(); + } + + @Override + public OutputStream getOutputStream() { + return writeWorkbookHolder.getOutputStream(); + } + + @Override + public Workbook getWorkbook() { + return writeWorkbookHolder.getWorkbook(); + } + + private void clearEncrypt03() { + if (StringUtils.isEmpty(writeWorkbookHolder.getPassword()) + || !ExcelTypeEnum.XLS.equals(writeWorkbookHolder.getExcelType())) { + return; + } + Biff8EncryptionKey.setCurrentUserPassword(null); + } + + /** + * To encrypt + */ + private boolean doOutputStreamEncrypt07() throws Exception { + if (StringUtils.isEmpty(writeWorkbookHolder.getPassword()) + || !ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) { + return false; + } + if (writeWorkbookHolder.getFile() != null) { + return false; + } + File tempXlsx = FileUtils.createTmpFile(UUID.randomUUID().toString() + ".xlsx"); + FileOutputStream tempFileOutputStream = new FileOutputStream(tempXlsx); + try { + writeWorkbookHolder.getWorkbook().write(tempFileOutputStream); + } + finally { + try { + writeWorkbookHolder.getWorkbook().close(); + tempFileOutputStream.close(); + } + catch (Exception e) { + if (!tempXlsx.delete()) { + throw new ExcelGenerateException("Can not delete temp File!"); + } + throw e; + } + } + POIFSFileSystem fileSystem = null; + try { + fileSystem = openFileSystemAndEncrypt(tempXlsx); + fileSystem.writeFilesystem(writeWorkbookHolder.getOutputStream()); + } + finally { + if (fileSystem != null) { + fileSystem.close(); + } + if (!tempXlsx.delete()) { + throw new ExcelGenerateException("Can not delete temp File!"); + } + } + return true; + } + + /** + * To encrypt + */ + private void doFileEncrypt07() throws Exception { + if (StringUtils.isEmpty(writeWorkbookHolder.getPassword()) + || !ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) { + return; + } + if (writeWorkbookHolder.getFile() == null) { + return; + } + FileOutputStream fileOutputStream = null; + POIFSFileSystem fileSystem = null; + try { + fileSystem = openFileSystemAndEncrypt(writeWorkbookHolder.getFile()); + fileOutputStream = new FileOutputStream(writeWorkbookHolder.getFile()); + fileSystem.writeFilesystem(fileOutputStream); + } + finally { + if (fileOutputStream != null) { + fileOutputStream.close(); + } + if (fileSystem != null) { + fileSystem.close(); + } + } + } + + private POIFSFileSystem openFileSystemAndEncrypt(File file) throws Exception { + POIFSFileSystem fileSystem = new POIFSFileSystem(); + Encryptor encryptor = new EncryptionInfo(EncryptionMode.standard).getEncryptor(); + encryptor.confirmPassword(writeWorkbookHolder.getPassword()); + OPCPackage opcPackage = null; + OutputStream outputStream = null; + try { + opcPackage = OPCPackage.open(file, PackageAccess.READ_WRITE); + outputStream = encryptor.getDataStream(fileSystem); + opcPackage.save(outputStream); + } + finally { + if (opcPackage != null) { + opcPackage.close(); + } + if (outputStream != null) { + outputStream.close(); + } + } + return fileSystem; + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/util/ClassUtils.java b/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/util/ClassUtils.java new file mode 100644 index 000000000..74d8912b4 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/util/ClassUtils.java @@ -0,0 +1,221 @@ +package com.alibaba.excel.util; + +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; +import com.alibaba.excel.metadata.Holder; +import com.alibaba.excel.write.metadata.holder.WriteHolder; +import lombok.extern.slf4j.Slf4j; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Class utils + * + * @author Jiaju Zhuang + **/ +@Slf4j +public class ClassUtils { + + static { + log.debug("======[ClassUtils] 修复版被加载了======="); + } + + private static final Map> FIELD_CACHE = new ConcurrentHashMap>(); + + public static void declaredFields(Class clazz, Map sortedAllFiledMap, + Map indexFiledMap, Map ignoreMap, Boolean convertAllFiled, + Boolean needIgnore, Holder holder) { + FieldCache fieldCache = getFieldCache(clazz, convertAllFiled); + if (fieldCache == null) { + return; + } + if (ignoreMap != null) { + ignoreMap.putAll(fieldCache.getIgnoreMap()); + } + Map tempIndexFildMap = indexFiledMap; + if (tempIndexFildMap == null) { + tempIndexFildMap = new TreeMap(); + } + tempIndexFildMap.putAll(fieldCache.getIndexFiledMap()); + + Map originSortedAllFiledMap = fieldCache.getSortedAllFiledMap(); + if (!needIgnore) { + sortedAllFiledMap.putAll(originSortedAllFiledMap); + return; + } + + // 获取到属性字段的最大index + int maxIndex = -1; + for (Integer filedIndex : originSortedAllFiledMap.keySet()) { + maxIndex = Math.max(filedIndex, maxIndex); + } + // 被忽略的属性数量 + int ignoreNum = 0; + // 当有属性被忽略时,需要将其后面的所有属性 index 前移 + for (int index = 0; index <= maxIndex; index++) { + Field field = originSortedAllFiledMap.get(index); + String name = field == null ? null : field.getName(); + if (((WriteHolder) holder).ignore(name, index)) { + if (ignoreMap != null && name != null) { + ignoreMap.put(name, field); + } + tempIndexFildMap.remove(index); + ignoreNum++; + } + else if (field != null) { + int finalIndex = index - ignoreNum; + sortedAllFiledMap.put(finalIndex, field); + } + } + } + + public static void declaredFields(Class clazz, Map sortedAllFiledMap, Boolean convertAllFiled, + Boolean needIgnore, WriteHolder writeHolder) { + declaredFields(clazz, sortedAllFiledMap, null, null, convertAllFiled, needIgnore, writeHolder); + } + + 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 + Map> orderFiledMap = new TreeMap>(); + Map indexFiledMap = new TreeMap(); + Map ignoreMap = new HashMap(16); + + ExcelIgnoreUnannotated excelIgnoreUnannotated = (ExcelIgnoreUnannotated) clazz + .getAnnotation(ExcelIgnoreUnannotated.class); + for (Field field : tempFieldList) { + declaredOneField(field, orderFiledMap, indexFiledMap, ignoreMap, excelIgnoreUnannotated, convertAllFiled); + } + FIELD_CACHE.put(clazz, new SoftReference( + new FieldCache(buildSortedAllFiledMap(orderFiledMap, indexFiledMap), indexFiledMap, ignoreMap))); + } + + private static Map buildSortedAllFiledMap(Map> orderFiledMap, + Map indexFiledMap) { + + Map sortedAllFiledMap = new HashMap( + (orderFiledMap.size() + indexFiledMap.size()) * 4 / 3 + 1); + + Map tempIndexFiledMap = new HashMap(indexFiledMap); + int index = 0; + for (List fieldList : orderFiledMap.values()) { + for (Field field : fieldList) { + while (tempIndexFiledMap.containsKey(index)) { + sortedAllFiledMap.put(index, tempIndexFiledMap.get(index)); + tempIndexFiledMap.remove(index); + index++; + } + sortedAllFiledMap.put(index, field); + index++; + } + } + sortedAllFiledMap.putAll(tempIndexFiledMap); + return sortedAllFiledMap; + } + + private static void declaredOneField(Field field, Map> orderFiledMap, + Map indexFiledMap, Map ignoreMap, + ExcelIgnoreUnannotated excelIgnoreUnannotated, Boolean convertAllFiled) { + ExcelIgnore excelIgnore = field.getAnnotation(ExcelIgnore.class); + if (excelIgnore != null) { + ignoreMap.put(field.getName(), field); + return; + } + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + boolean noExcelProperty = excelProperty == null + && ((convertAllFiled != null && !convertAllFiled) || excelIgnoreUnannotated != null); + if (noExcelProperty) { + ignoreMap.put(field.getName(), field); + return; + } + boolean isStaticFinalOrTransient = (Modifier.isStatic(field.getModifiers()) + && Modifier.isFinal(field.getModifiers())) || Modifier.isTransient(field.getModifiers()); + if (excelProperty == null && isStaticFinalOrTransient) { + ignoreMap.put(field.getName(), field); + return; + } + if (excelProperty != null && excelProperty.index() >= 0) { + if (indexFiledMap.containsKey(excelProperty.index())) { + throw new ExcelCommonException("The index of '" + indexFiledMap.get(excelProperty.index()).getName() + + "' and '" + field.getName() + "' must be inconsistent"); + } + indexFiledMap.put(excelProperty.index(), field); + return; + } + + int order = Integer.MAX_VALUE; + if (excelProperty != null) { + order = excelProperty.order(); + } + List orderFiledList = orderFiledMap.get(order); + if (orderFiledList == null) { + orderFiledList = new ArrayList(); + orderFiledMap.put(order, orderFiledList); + } + orderFiledList.add(field); + } + + private static class FieldCache { + + private Map sortedAllFiledMap; + + private Map indexFiledMap; + + private Map ignoreMap; + + public FieldCache(Map sortedAllFiledMap, Map indexFiledMap, + Map ignoreMap) { + this.sortedAllFiledMap = sortedAllFiledMap; + this.indexFiledMap = indexFiledMap; + this.ignoreMap = ignoreMap; + } + + public Map getSortedAllFiledMap() { + return sortedAllFiledMap; + } + + public Map getIndexFiledMap() { + return indexFiledMap; + } + + public Map getIgnoreMap() { + return ignoreMap; + } + + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java b/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java new file mode 100644 index 000000000..faeec8d8a --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/alibaba/excel/write/executor/ExcelWriteAddExecutor.java @@ -0,0 +1,185 @@ +package com.alibaba.excel.write.executor; + +import com.alibaba.excel.context.WriteContext; +import com.alibaba.excel.enums.HeadKindEnum; +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; +import com.alibaba.excel.write.metadata.holder.AbstractWriteHolder; +import com.alibaba.excel.write.metadata.holder.WriteHolder; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import lombok.extern.slf4j.Slf4j; +import net.sf.cglib.beans.BeanMap; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * Add the data into excel + * + * @author Jiaju Zhuang + */ +@Slf4j +public class ExcelWriteAddExecutor extends AbstractExcelWriteExecutor { + + static { + log.debug("======[ExcelWriteAddExecutor] 修复版被加载了======="); + } + + public ExcelWriteAddExecutor(WriteContext writeContext) { + super(writeContext); + } + + public void add(List data) { + if (CollectionUtils.isEmpty(data)) { + data = new ArrayList(); + } + WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder(); + int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); + if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) { + newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex(); + } + // BeanMap is out of order,so use sortedAllFiledMap + Map sortedAllFiledMap = new TreeMap(); + int relativeRowIndex = 0; + for (Object oneRowData : data) { + int n = relativeRowIndex + newRowIndex; + addOneRowOfDataToExcel(oneRowData, n, relativeRowIndex, sortedAllFiledMap); + relativeRowIndex++; + } + } + + private void addOneRowOfDataToExcel(Object oneRowData, int n, int relativeRowIndex, + Map sortedAllFiledMap) { + if (oneRowData == null) { + return; + } + WriteHandlerUtils.beforeRowCreate(writeContext, n, relativeRowIndex, Boolean.FALSE); + Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), n); + WriteHandlerUtils.afterRowCreate(writeContext, row, relativeRowIndex, Boolean.FALSE); + if (oneRowData instanceof List) { + addBasicTypeToExcel((List) oneRowData, row, relativeRowIndex); + } + else { + addJavaObjectToExcel(oneRowData, row, relativeRowIndex, sortedAllFiledMap); + } + WriteHandlerUtils.afterRowDispose(writeContext, row, relativeRowIndex, Boolean.FALSE); + } + + private void addBasicTypeToExcel(List oneRowData, Row row, int relativeRowIndex) { + if (CollectionUtils.isEmpty(oneRowData)) { + return; + } + Map headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap(); + int dataIndex = 0; + int cellIndex = 0; + for (Map.Entry entry : headMap.entrySet()) { + if (dataIndex >= oneRowData.size()) { + return; + } + cellIndex = entry.getKey(); + Head head = entry.getValue(); + doAddBasicTypeToExcel(oneRowData, head, row, relativeRowIndex, dataIndex++, cellIndex); + } + // Finish + if (dataIndex >= oneRowData.size()) { + return; + } + if (cellIndex != 0) { + cellIndex++; + } + int size = oneRowData.size() - dataIndex; + for (int i = 0; i < size; i++) { + doAddBasicTypeToExcel(oneRowData, null, row, relativeRowIndex, dataIndex++, cellIndex++); + } + } + + private void doAddBasicTypeToExcel(List oneRowData, Head head, Row row, int relativeRowIndex, int dataIndex, + int cellIndex) { + WriteHandlerUtils.beforeCellCreate(writeContext, row, head, cellIndex, relativeRowIndex, Boolean.FALSE); + Cell cell = WorkBookUtil.createCell(row, cellIndex); + WriteHandlerUtils.afterCellCreate(writeContext, cell, head, relativeRowIndex, Boolean.FALSE); + Object value = oneRowData.get(dataIndex); + CellData cellData = converterAndSet(writeContext.currentWriteHolder(), value == null ? null : value.getClass(), + cell, value, null, head, relativeRowIndex); + WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, head, relativeRowIndex, Boolean.FALSE); + } + + private void addJavaObjectToExcel(Object oneRowData, Row row, int relativeRowIndex, + Map sortedAllFiledMap) { + WriteHolder currentWriteHolder = writeContext.currentWriteHolder(); + BeanMap beanMap = BeanMap.create(oneRowData); + Set beanMapHandledSet = new HashSet(); + int cellIndex = 0; + // If it's a class it needs to be cast by type + if (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) { + Map headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap(); + Map contentPropertyMap = writeContext.currentWriteHolder() + .excelWriteHeadProperty().getContentPropertyMap(); + for (Map.Entry entry : contentPropertyMap.entrySet()) { + cellIndex = entry.getKey(); + ExcelContentProperty excelContentProperty = entry.getValue(); + String name = excelContentProperty.getField().getName(); + if (!beanMap.containsKey(name)) { + continue; + } + Head head = headMap.get(cellIndex); + WriteHandlerUtils.beforeCellCreate(writeContext, row, head, cellIndex, relativeRowIndex, Boolean.FALSE); + Cell cell = WorkBookUtil.createCell(row, cellIndex); + WriteHandlerUtils.afterCellCreate(writeContext, cell, head, relativeRowIndex, Boolean.FALSE); + Object value = beanMap.get(name); + CellData cellData = converterAndSet(currentWriteHolder, excelContentProperty.getField().getType(), cell, + value, excelContentProperty, head, relativeRowIndex); + WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, head, relativeRowIndex, Boolean.FALSE); + beanMapHandledSet.add(name); + } + } + // Finish + if (beanMapHandledSet.size() == beanMap.size()) { + return; + } + Map ignoreMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getIgnoreMap(); + initSortedAllFiledMapFieldList(oneRowData.getClass(), sortedAllFiledMap); + for (Map.Entry entry : sortedAllFiledMap.entrySet()) { + cellIndex = entry.getKey(); + Field field = entry.getValue(); + String filedName = field.getName(); + boolean uselessData = !beanMap.containsKey(filedName) || beanMapHandledSet.contains(filedName) + || ignoreMap.containsKey(filedName); + if (uselessData) { + continue; + } + Object value = beanMap.get(filedName); + 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 == null ? null : value.getClass(), cell, + value, null, null, relativeRowIndex); + WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, null, relativeRowIndex, Boolean.FALSE); + } + } + + private void initSortedAllFiledMapFieldList(Class clazz, Map sortedAllFiledMap) { + if (!sortedAllFiledMap.isEmpty()) { + return; + } + + // 获取当前的使用的 holder + WriteHolder holder = writeContext.currentWriteHolder(); + boolean needIgnore = (holder instanceof AbstractWriteHolder) + && (!CollectionUtils.isEmpty(((AbstractWriteHolder) holder).getExcludeColumnFiledNames()) + || !CollectionUtils.isEmpty(((AbstractWriteHolder) holder).getExcludeColumnIndexes()) + || !CollectionUtils.isEmpty(((AbstractWriteHolder) holder).getIncludeColumnFiledNames()) + || !CollectionUtils.isEmpty(((AbstractWriteHolder) holder).getIncludeColumnIndexes())); + + ClassUtils.declaredFields(clazz, sortedAllFiledMap, + writeContext.writeWorkbookHolder().getWriteWorkbook().getConvertAllFiled(), needIgnore, holder); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/ExcelHandlerConfiguration.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/ExcelHandlerConfiguration.java new file mode 100644 index 000000000..7a36dd22f --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/ExcelHandlerConfiguration.java @@ -0,0 +1,85 @@ +package com.pig4cloud.pigx.common.excel; + +import com.alibaba.excel.converters.Converter; +import com.pig4cloud.pigx.common.excel.aop.ResponseExcelReturnValueHandler; +import com.pig4cloud.pigx.common.excel.config.ExcelConfigProperties; +import com.pig4cloud.pigx.common.excel.enhance.DefaultWriterBuilderEnhancer; +import com.pig4cloud.pigx.common.excel.enhance.WriterBuilderEnhancer; +import com.pig4cloud.pigx.common.excel.handler.ManySheetWriteHandler; +import com.pig4cloud.pigx.common.excel.handler.SheetWriteHandler; +import com.pig4cloud.pigx.common.excel.handler.SingleSheetWriteHandler; +import com.pig4cloud.pigx.common.excel.head.I18nHeaderCellWriteHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * @author Hccake 2020/10/28 + * @version 1.0 + */ +@RequiredArgsConstructor +@Configuration +public class ExcelHandlerConfiguration { + + private final ExcelConfigProperties configProperties; + + private final ObjectProvider>> converterProvider; + + /** + * ExcelBuild增强 + * @return DefaultWriterBuilderEnhancer 默认什么也不做的增强器 + */ + @Bean + @ConditionalOnMissingBean + public WriterBuilderEnhancer writerBuilderEnhancer() { + return new DefaultWriterBuilderEnhancer(); + } + + /** + * 单sheet 写入处理器 + */ + @Bean + @ConditionalOnMissingBean + public SingleSheetWriteHandler singleSheetWriteHandler() { + return new SingleSheetWriteHandler(configProperties, converterProvider, writerBuilderEnhancer()); + } + + /** + * 多sheet 写入处理器 + */ + @Bean + @ConditionalOnMissingBean + public ManySheetWriteHandler manySheetWriteHandler() { + return new ManySheetWriteHandler(configProperties, converterProvider, writerBuilderEnhancer()); + } + + /** + * 返回Excel文件的 response 处理器 + * @param sheetWriteHandlerList 页签写入处理器集合 + * @return ResponseExcelReturnValueHandler + */ + @Bean + @ConditionalOnMissingBean + public ResponseExcelReturnValueHandler responseExcelReturnValueHandler( + List sheetWriteHandlerList) { + return new ResponseExcelReturnValueHandler(sheetWriteHandlerList); + } + + /** + * excel 头的国际化处理器 + * @param messageSource 国际化源 + */ + @Bean + @ConditionalOnBean(MessageSource.class) + @ConditionalOnMissingBean + public I18nHeaderCellWriteHandler i18nHeaderCellWriteHandler(MessageSource messageSource) { + return new I18nHeaderCellWriteHandler(messageSource); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/ResponseExcelAutoConfiguration.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/ResponseExcelAutoConfiguration.java new file mode 100644 index 000000000..bb1aed3a6 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/ResponseExcelAutoConfiguration.java @@ -0,0 +1,87 @@ +package com.pig4cloud.pigx.common.excel; + +import com.pig4cloud.pigx.common.excel.aop.DynamicNameAspect; +import com.pig4cloud.pigx.common.excel.aop.RequestExcelArgumentResolver; +import com.pig4cloud.pigx.common.excel.aop.ResponseExcelReturnValueHandler; +import com.pig4cloud.pigx.common.excel.config.ExcelConfigProperties; +import com.pig4cloud.pigx.common.excel.processor.NameProcessor; +import com.pig4cloud.pigx.common.excel.processor.NameSpelExpressionProcessor; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +/** + * @author lengleng + * @date 2020/3/29 + *

+ * 配置初始化 + */ +@Import(ExcelHandlerConfiguration.class) +@RequiredArgsConstructor +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(ExcelConfigProperties.class) +public class ResponseExcelAutoConfiguration { + + private final RequestMappingHandlerAdapter requestMappingHandlerAdapter; + + private final ResponseExcelReturnValueHandler responseExcelReturnValueHandler; + + /** + * SPEL 解析处理器 + * @return NameProcessor excel名称解析器 + */ + @Bean + @ConditionalOnMissingBean + public NameProcessor nameProcessor() { + return new NameSpelExpressionProcessor(); + } + + /** + * Excel名称解析处理切面 + * @param nameProcessor SPEL 解析处理器 + * @return DynamicNameAspect + */ + @Bean + @ConditionalOnMissingBean + public DynamicNameAspect dynamicNameAspect(NameProcessor nameProcessor) { + return new DynamicNameAspect(nameProcessor); + } + + /** + * 追加 Excel返回值处理器 到 springmvc 中 + */ + @PostConstruct + public void setReturnValueHandlers() { + List returnValueHandlers = requestMappingHandlerAdapter + .getReturnValueHandlers(); + + List newHandlers = new ArrayList<>(); + newHandlers.add(responseExcelReturnValueHandler); + assert returnValueHandlers != null; + newHandlers.addAll(returnValueHandlers); + requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers); + } + + /** + * 追加 Excel 请求处理器 到 springmvc 中 + */ + @PostConstruct + public void setRequestExcelArgumentResolver() { + List argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); + List resolverList = new ArrayList<>(); + resolverList.add(new RequestExcelArgumentResolver()); + resolverList.addAll(argumentResolvers); + requestMappingHandlerAdapter.setArgumentResolvers(resolverList); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/RequestExcel.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/RequestExcel.java new file mode 100644 index 000000000..979594891 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/RequestExcel.java @@ -0,0 +1,37 @@ +package com.pig4cloud.pigx.common.excel.annotation; + +import com.pig4cloud.pigx.common.excel.handler.DefaultAnalysisEventListener; +import com.pig4cloud.pigx.common.excel.handler.ListAnalysisEventListener; + +import java.lang.annotation.*; + +/** + * 导入excel + * + * @author lengleng + * @author L.cm + * @date 2021/4/16 + */ +@Documented +@Target({ ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequestExcel { + + /** + * 前端上传字段名称 file + */ + String fileName() default "file"; + + /** + * 读取的监听器类 + * @return readListener + */ + Class> readListener() default DefaultAnalysisEventListener.class; + + /** + * 是否跳过空行 + * @return 默认跳过 + */ + boolean ignoreEmptyRow() default false; + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/ResponseExcel.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/ResponseExcel.java new file mode 100644 index 000000000..8e8a3865f --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/ResponseExcel.java @@ -0,0 +1,92 @@ +package com.pig4cloud.pigx.common.excel.annotation; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.support.ExcelTypeEnum; +import com.alibaba.excel.write.handler.WriteHandler; +import com.pig4cloud.pigx.common.excel.head.HeadGenerator; + +import java.lang.annotation.*; + +/** + * `@ResponseExcel 注解` + * + * @author lengleng + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ResponseExcel { + + /** + * 文件名称 + * @return string + */ + String name() default ""; + + /** + * 文件类型 (xlsx xls) + * @return string + */ + ExcelTypeEnum suffix() default ExcelTypeEnum.XLSX; + + /** + * 文件密码 + * @return password + */ + String password() default ""; + + /** + * sheet 名称,支持多个 + * @return String[] + */ + Sheet[] sheets() default @Sheet(sheetName = "sheet1"); + + /** + * 内存操作 + * @return + */ + boolean inMemory() default false; + + /** + * excel 模板 + * @return String + */ + String template() default ""; + + /** + * + 包含字段 + * @return String[] + */ + String[] include() default {}; + + /** + * 排除字段 + * @return String[] + */ + String[] exclude() default {}; + + /** + * 拦截器,自定义样式等处理器 + * @return WriteHandler[] + */ + Class[] writeHandler() default {}; + + /** + * 转换器 + * @return Converter[] + */ + Class[] converter() default {}; + + /** + * 自定义Excel头生成器 + * @return HeadGenerator + */ + Class headGenerator() default HeadGenerator.class; + + /** + * excel 头信息国际化 + * @return boolean + */ + boolean i18nHeader() default false; + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/Sheet.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/Sheet.java new file mode 100644 index 000000000..698ed0959 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/annotation/Sheet.java @@ -0,0 +1,41 @@ +package com.pig4cloud.pigx.common.excel.annotation; + +import com.pig4cloud.pigx.common.excel.head.HeadGenerator; + +import java.lang.annotation.*; + +/** + * @author Yakir + * @Topic Sheet + * @Description + * @date 2021/4/29 15:03 + * @Version 1.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Sheet { + + int sheetNo() default -1; + + /** + * sheet name + */ + String sheetName(); + + /** + * 包含字段 + */ + String[] includes() default {}; + + /** + * 排除字段 + */ + String[] excludes() default {}; + + /** + * 头生成器 + */ + Class headGenerateClass() default HeadGenerator.class; + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/DynamicNameAspect.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/DynamicNameAspect.java new file mode 100644 index 000000000..0b462598c --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/DynamicNameAspect.java @@ -0,0 +1,46 @@ +package com.pig4cloud.pigx.common.excel.aop; + +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.processor.NameProcessor; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.time.LocalDateTime; +import java.util.Objects; + +/** + * @author lengleng + * @date 2020/3/29 + */ +@Aspect +@RequiredArgsConstructor +public class DynamicNameAspect { + + public static final String EXCEL_NAME_KEY = "__EXCEL_NAME_KEY__"; + + private final NameProcessor processor; + + @Before("@annotation(excel)") + public void around(JoinPoint point, ResponseExcel excel) { + MethodSignature ms = (MethodSignature) point.getSignature(); + + String name = excel.name(); + // 当配置的 excel 名称为空时,取当前时间 + if (!StringUtils.hasText(name)) { + name = LocalDateTime.now().toString(); + } + else { + name = processor.doDetermineName(point.getArgs(), ms.getMethod(), excel.name()); + } + + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + Objects.requireNonNull(requestAttributes).setAttribute(EXCEL_NAME_KEY, name, RequestAttributes.SCOPE_REQUEST); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/RequestExcelArgumentResolver.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/RequestExcelArgumentResolver.java new file mode 100644 index 000000000..c2d99ce76 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/RequestExcelArgumentResolver.java @@ -0,0 +1,87 @@ +package com.pig4cloud.pigx.common.excel.aop; + +import com.alibaba.excel.EasyExcel; +import com.pig4cloud.pigx.common.excel.annotation.RequestExcel; +import com.pig4cloud.pigx.common.excel.converters.LocalDateStringConverter; +import com.pig4cloud.pigx.common.excel.converters.LocalDateTimeStringConverter; +import com.pig4cloud.pigx.common.excel.handler.ListAnalysisEventListener; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.core.MethodParameter; +import org.springframework.core.ResolvableType; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartRequest; + +import javax.servlet.http.HttpServletRequest; +import java.io.InputStream; +import java.util.List; + +/** + * 上传excel 解析注解 + * + * @author lengleng + * @author L.cm + * @date 2021/4/16 + */ +@Slf4j +public class RequestExcelArgumentResolver implements HandlerMethodArgumentResolver { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(RequestExcel.class); + } + + @Override + @SneakyThrows + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer modelAndViewContainer, + NativeWebRequest webRequest, WebDataBinderFactory webDataBinderFactory) { + Class parameterType = parameter.getParameterType(); + if (!parameterType.isAssignableFrom(List.class)) { + throw new IllegalArgumentException( + "Excel upload request resolver error, @RequestExcel parameter is not List " + parameterType); + } + + // 处理自定义 readListener + RequestExcel requestExcel = parameter.getParameterAnnotation(RequestExcel.class); + assert requestExcel != null; + Class> readListenerClass = requestExcel.readListener(); + ListAnalysisEventListener readListener = BeanUtils.instantiateClass(readListenerClass); + + // 获取请求文件流 + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + assert request != null; + InputStream inputStream; + if (request instanceof MultipartRequest) { + MultipartFile file = ((MultipartRequest) request).getFile(requestExcel.fileName()); + assert file != null; + inputStream = file.getInputStream(); + } + else { + inputStream = request.getInputStream(); + } + + // 获取目标类型 + Class excelModelClass = ResolvableType.forMethodParameter(parameter).getGeneric(0).resolve(); + + // 这里需要指定读用哪个 class 去读,然后读取第一个 sheet 文件流会自动关闭 + EasyExcel.read(inputStream, excelModelClass, readListener).registerConverter(LocalDateStringConverter.INSTANCE) + .registerConverter(LocalDateTimeStringConverter.INSTANCE).ignoreEmptyRow(requestExcel.ignoreEmptyRow()) + .sheet().doRead(); + + // 校验失败的数据处理 交给 BindResult + WebDataBinder dataBinder = webDataBinderFactory.createBinder(webRequest, readListener.getErrors(), "excel"); + ModelMap model = modelAndViewContainer.getModel(); + model.put(BindingResult.MODEL_KEY_PREFIX + "excel", dataBinder.getBindingResult()); + + return readListener.getList(); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/ResponseExcelReturnValueHandler.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/ResponseExcelReturnValueHandler.java new file mode 100644 index 000000000..d13e390d5 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/aop/ResponseExcelReturnValueHandler.java @@ -0,0 +1,59 @@ +package com.pig4cloud.pigx.common.excel.aop; + +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.handler.SheetWriteHandler; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 处理@ResponseExcel 返回值 + * + * @author lengleng + */ +@Slf4j +@RequiredArgsConstructor +public class ResponseExcelReturnValueHandler implements HandlerMethodReturnValueHandler { + + private final List sheetWriteHandlerList; + + /** + * 只处理@ResponseExcel 声明的方法 + * @param parameter 方法签名 + * @return 是否处理 + */ + @Override + public boolean supportsReturnType(MethodParameter parameter) { + return parameter.getMethodAnnotation(ResponseExcel.class) != null; + } + + /** + * 处理逻辑 + * @param o 返回参数 + * @param parameter 方法签名 + * @param mavContainer 上下文容器 + * @param nativeWebRequest 上下文 + * @throws Exception 处理异常 + */ + @Override + public void handleReturnValue(Object o, MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest nativeWebRequest) throws Exception { + /* check */ + HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class); + Assert.state(response != null, "No HttpServletResponse"); + ResponseExcel responseExcel = parameter.getMethodAnnotation(ResponseExcel.class); + Assert.state(responseExcel != null, "No @ResponseExcel"); + mavContainer.setRequestHandled(true); + + sheetWriteHandlerList.stream().filter(handler -> handler.support(o)).findFirst() + .ifPresent(handler -> handler.export(o, response, responseExcel)); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/config/ExcelConfigProperties.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/config/ExcelConfigProperties.java new file mode 100644 index 000000000..d27f4f5c2 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/config/ExcelConfigProperties.java @@ -0,0 +1,21 @@ +package com.pig4cloud.pigx.common.excel.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author lengleng + * @date 2020/3/29 + */ +@Data +@ConfigurationProperties(prefix = ExcelConfigProperties.PREFIX) +public class ExcelConfigProperties { + + static final String PREFIX = "excel"; + + /** + * 模板路径 + */ + private String templatePath = "excel"; + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/converters/LocalDateStringConverter.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/converters/LocalDateStringConverter.java new file mode 100644 index 000000000..b43c5ba0a --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/converters/LocalDateStringConverter.java @@ -0,0 +1,61 @@ +package com.pig4cloud.pigx.common.excel.converters; + +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 java.text.ParseException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * LocalDate and string converter + * + * @author L.cm + */ +public enum LocalDateStringConverter implements Converter { + + /** + * 实例 + */ + INSTANCE; + + @Override + public Class supportJavaTypeKey() { + return LocalDate.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public LocalDate convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + return LocalDate.parse(cellData.getStringValue()); + } + else { + DateTimeFormatter formatter = DateTimeFormatter + .ofPattern(contentProperty.getDateTimeFormatProperty().getFormat()); + return LocalDate.parse(cellData.getStringValue(), formatter); + } + } + + @Override + public CellData convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + DateTimeFormatter formatter; + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + formatter = DateTimeFormatter.ISO_LOCAL_DATE; + } + else { + formatter = DateTimeFormatter.ofPattern(contentProperty.getDateTimeFormatProperty().getFormat()); + } + return new CellData<>(value.format(formatter)); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/converters/LocalDateTimeStringConverter.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/converters/LocalDateTimeStringConverter.java new file mode 100644 index 000000000..a326ea03a --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/converters/LocalDateTimeStringConverter.java @@ -0,0 +1,93 @@ +package com.pig4cloud.pigx.common.excel.converters; + +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.DateUtils; + +import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * LocalDateTime and string converter + * + * @author L.cm + */ +public enum LocalDateTimeStringConverter implements Converter { + + /** + * 实例 + */ + INSTANCE; + + private static final String MINUS = "-"; + + @Override + public Class supportJavaTypeKey() { + return LocalDateTime.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public LocalDateTime convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) throws ParseException { + String stringValue = cellData.getStringValue(); + String pattern; + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + pattern = switchDateFormat(stringValue); + } + else { + pattern = contentProperty.getDateTimeFormatProperty().getFormat(); + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.parse(cellData.getStringValue(), formatter); + } + + @Override + public CellData convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + String pattern; + if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) { + pattern = DateUtils.DATE_FORMAT_19; + } + else { + pattern = contentProperty.getDateTimeFormatProperty().getFormat(); + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return new CellData<>(value.format(formatter)); + } + + /** + * switch date format + * @param dateString dateString + * @return pattern + */ + private static String switchDateFormat(String dateString) { + int length = dateString.length(); + switch (length) { + case 19: + if (dateString.contains(MINUS)) { + return DateUtils.DATE_FORMAT_19; + } + else { + return DateUtils.DATE_FORMAT_19_FORWARD_SLASH; + } + case 17: + return DateUtils.DATE_FORMAT_17; + case 14: + return DateUtils.DATE_FORMAT_14; + case 10: + return DateUtils.DATE_FORMAT_10; + default: + throw new IllegalArgumentException("can not find date format for:" + dateString); + } + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/enhance/DefaultWriterBuilderEnhancer.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/enhance/DefaultWriterBuilderEnhancer.java new file mode 100644 index 000000000..bc0c7266e --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/enhance/DefaultWriterBuilderEnhancer.java @@ -0,0 +1,48 @@ +package com.pig4cloud.pigx.common.excel.enhance; + +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.head.HeadGenerator; + +import javax.servlet.http.HttpServletResponse; + +/** + * @author Hccake 2020/12/18 + * @version 1.0 + */ +public class DefaultWriterBuilderEnhancer implements WriterBuilderEnhancer { + + /** + * ExcelWriterBuilder 增强 + * @param writerBuilder ExcelWriterBuilder + * @param response HttpServletResponse + * @param responseExcel ResponseExcel + * @param templatePath 模板地址 + * @return ExcelWriterBuilder + */ + @Override + public ExcelWriterBuilder enhanceExcel(ExcelWriterBuilder writerBuilder, HttpServletResponse response, + ResponseExcel responseExcel, String templatePath) { + // doNothing + return writerBuilder; + } + + /** + * ExcelWriterSheetBuilder 增强 + * @param writerSheetBuilder ExcelWriterSheetBuilder + * @param sheetNo sheet角标 + * @param sheetName sheet名,有模板时为空 + * @param dataClass 当前写入的数据所属类 + * @param template 模板文件 + * @param headEnhancerClass 当前指定的自定义头处理器 + * @return ExcelWriterSheetBuilder + */ + @Override + public ExcelWriterSheetBuilder enhanceSheet(ExcelWriterSheetBuilder writerSheetBuilder, Integer sheetNo, + String sheetName, Class dataClass, String template, Class headEnhancerClass) { + // doNothing + return writerSheetBuilder; + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/enhance/WriterBuilderEnhancer.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/enhance/WriterBuilderEnhancer.java new file mode 100644 index 000000000..4bbeab76a --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/enhance/WriterBuilderEnhancer.java @@ -0,0 +1,42 @@ +package com.pig4cloud.pigx.common.excel.enhance; + +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.head.HeadGenerator; + +import javax.servlet.http.HttpServletResponse; + +/** + * ExcelWriterBuilder 增强 + * + * @author Hccake 2020/12/18 + * @version 1.0 + */ +public interface WriterBuilderEnhancer { + + /** + * ExcelWriterBuilder 增强 + * @param writerBuilder ExcelWriterBuilder + * @param response HttpServletResponse + * @param responseExcel ResponseExcel + * @param templatePath 模板地址 + * @return ExcelWriterBuilder + */ + ExcelWriterBuilder enhanceExcel(ExcelWriterBuilder writerBuilder, HttpServletResponse response, + ResponseExcel responseExcel, String templatePath); + + /** + * ExcelWriterSheetBuilder 增强 + * @param writerSheetBuilder ExcelWriterSheetBuilder + * @param sheetNo sheet角标 + * @param sheetName sheet名,有模板时为空 + * @param dataClass 当前写入的数据所属类 + * @param template 模板文件 + * @param headEnhancerClass 当前指定的自定义头处理器 + * @return ExcelWriterSheetBuilder + */ + ExcelWriterSheetBuilder enhanceSheet(ExcelWriterSheetBuilder writerSheetBuilder, Integer sheetNo, String sheetName, + Class dataClass, String template, Class headEnhancerClass); + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/AbstractSheetWriteHandler.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/AbstractSheetWriteHandler.java new file mode 100644 index 000000000..b8ec78423 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/AbstractSheetWriteHandler.java @@ -0,0 +1,231 @@ +package com.pig4cloud.pigx.common.excel.handler; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.write.builder.ExcelWriterBuilder; +import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; +import com.alibaba.excel.write.handler.WriteHandler; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.annotation.Sheet; +import com.pig4cloud.pigx.common.excel.aop.DynamicNameAspect; +import com.pig4cloud.pigx.common.excel.config.ExcelConfigProperties; +import com.pig4cloud.pigx.common.excel.converters.LocalDateStringConverter; +import com.pig4cloud.pigx.common.excel.converters.LocalDateTimeStringConverter; +import com.pig4cloud.pigx.common.excel.enhance.WriterBuilderEnhancer; +import com.pig4cloud.pigx.common.excel.head.HeadGenerator; +import com.pig4cloud.pigx.common.excel.head.HeadMeta; +import com.pig4cloud.pigx.common.excel.head.I18nHeaderCellWriteHandler; +import com.pig4cloud.pigx.common.excel.kit.ExcelException; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.SneakyThrows; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * @author lengleng + * @author L.cm + * @date 2020/3/31 + */ +@RequiredArgsConstructor +public abstract class AbstractSheetWriteHandler implements SheetWriteHandler, ApplicationContextAware { + + private final ExcelConfigProperties configProperties; + + private final ObjectProvider>> converterProvider; + + private final WriterBuilderEnhancer excelWriterBuilderEnhance; + + private ApplicationContext applicationContext; + + @Getter + @Setter + @Autowired(required = false) + private I18nHeaderCellWriteHandler i18nHeaderCellWriteHandler; + + @Override + public void check(ResponseExcel responseExcel) { + if (responseExcel.sheets().length == 0) { + throw new ExcelException("@ResponseExcel sheet 配置不合法"); + } + } + + @Override + @SneakyThrows + public void export(Object o, HttpServletResponse response, ResponseExcel responseExcel) { + check(responseExcel); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + String name = (String) Objects.requireNonNull(requestAttributes).getAttribute(DynamicNameAspect.EXCEL_NAME_KEY, + RequestAttributes.SCOPE_REQUEST); + String fileName = String.format("%s%s", URLEncoder.encode(name, "UTF-8"), responseExcel.suffix().getValue()); + // 根据实际的文件类型找到对应的 contentType + String contentType = MediaTypeFactory.getMediaType(fileName).map(MediaType::toString) + .orElse("application/vnd.ms-excel"); + response.setContentType(contentType); + response.setCharacterEncoding("utf-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName); + write(o, response, responseExcel); + } + + /** + * 通用的获取ExcelWriter方法 + * @param response HttpServletResponse + * @param responseExcel ResponseExcel注解 + * @return ExcelWriter + */ + @SneakyThrows + public ExcelWriter getExcelWriter(HttpServletResponse response, ResponseExcel responseExcel) { + ExcelWriterBuilder writerBuilder = EasyExcel.write(response.getOutputStream()) + .registerConverter(LocalDateStringConverter.INSTANCE) + .registerConverter(LocalDateTimeStringConverter.INSTANCE).autoCloseStream(true) + .excelType(responseExcel.suffix()).inMemory(responseExcel.inMemory()); + + if (StringUtils.hasText(responseExcel.password())) { + writerBuilder.password(responseExcel.password()); + } + + if (responseExcel.include().length != 0) { + writerBuilder.includeColumnFiledNames(Arrays.asList(responseExcel.include())); + } + + if (responseExcel.exclude().length != 0) { + writerBuilder.excludeColumnFiledNames(Arrays.asList(responseExcel.exclude())); + } + + if (responseExcel.writeHandler().length != 0) { + for (Class clazz : responseExcel.writeHandler()) { + writerBuilder.registerWriteHandler(BeanUtils.instantiateClass(clazz)); + } + } + + // 开启国际化头信息处理 + if (responseExcel.i18nHeader() && i18nHeaderCellWriteHandler != null) { + writerBuilder.registerWriteHandler(i18nHeaderCellWriteHandler); + } + + // 自定义注入的转换器 + registerCustomConverter(writerBuilder); + + if (responseExcel.converter().length != 0) { + for (Class clazz : responseExcel.converter()) { + writerBuilder.registerConverter(BeanUtils.instantiateClass(clazz)); + } + } + + String templatePath = configProperties.getTemplatePath(); + if (StringUtils.hasText(responseExcel.template())) { + ClassPathResource classPathResource = new ClassPathResource( + templatePath + File.separator + responseExcel.template()); + InputStream inputStream = classPathResource.getInputStream(); + writerBuilder.withTemplate(inputStream); + } + + writerBuilder = excelWriterBuilderEnhance.enhanceExcel(writerBuilder, response, responseExcel, templatePath); + + return writerBuilder.build(); + } + + /** + * 自定义注入转换器 如果有需要,子类自己重写 + * @param builder ExcelWriterBuilder + */ + public void registerCustomConverter(ExcelWriterBuilder builder) { + converterProvider.ifAvailable(converters -> converters.forEach(builder::registerConverter)); + } + + /** + * 获取 WriteSheet 对象 + * @param sheet sheet annotation info + * @param dataClass 数据类型 + * @param template 模板 + * @param bookHeadEnhancerClass 自定义头处理器 + * @return WriteSheet + */ + public WriteSheet sheet(Sheet sheet, Class dataClass, String template, + Class bookHeadEnhancerClass) { + + // Sheet 编号和名称 + Integer sheetNo = sheet.sheetNo() >= 0 ? sheet.sheetNo() : null; + String sheetName = sheet.sheetName(); + + // 是否模板写入 + ExcelWriterSheetBuilder writerSheetBuilder = StringUtils.hasText(template) ? EasyExcel.writerSheet(sheetNo) + : EasyExcel.writerSheet(sheetNo, sheetName); + + // 头信息增强 1. 优先使用 sheet 指定的头信息增强 2. 其次使用 @ResponseExcel 中定义的全局头信息增强 + Class headGenerateClass = null; + if (isNotInterface(sheet.headGenerateClass())) { + headGenerateClass = sheet.headGenerateClass(); + } + else if (isNotInterface(bookHeadEnhancerClass)) { + headGenerateClass = bookHeadEnhancerClass; + } + // 定义头信息增强则使用其生成头信息,否则使用 dataClass 来自动获取 + if (headGenerateClass != null) { + fillCustomHeadInfo(dataClass, bookHeadEnhancerClass, writerSheetBuilder); + } + else if (dataClass != null) { + writerSheetBuilder.head(dataClass); + if (sheet.excludes().length > 0) { + writerSheetBuilder.excludeColumnFiledNames(Arrays.asList(sheet.excludes())); + } + if (sheet.includes().length > 0) { + writerSheetBuilder.includeColumnFiledNames(Arrays.asList(sheet.includes())); + } + } + + // sheetBuilder 增强 + writerSheetBuilder = excelWriterBuilderEnhance.enhanceSheet(writerSheetBuilder, sheetNo, sheetName, dataClass, + template, headGenerateClass); + + return writerSheetBuilder.build(); + } + + private void fillCustomHeadInfo(Class dataClass, Class headEnhancerClass, + ExcelWriterSheetBuilder writerSheetBuilder) { + HeadGenerator headGenerator = this.applicationContext.getBean(headEnhancerClass); + Assert.notNull(headGenerator, "The header generated bean does not exist."); + HeadMeta head = headGenerator.head(dataClass); + writerSheetBuilder.head(head.getHead()); + writerSheetBuilder.excludeColumnFiledNames(head.getIgnoreHeadFields()); + } + + /** + * 是否为Null Head Generator + * @param headGeneratorClass + * @return true 已指定 false 未指定(默认值) + */ + private boolean isNotInterface(Class headGeneratorClass) { + return !Modifier.isInterface(headGeneratorClass.getModifiers()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/DefaultAnalysisEventListener.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/DefaultAnalysisEventListener.java new file mode 100644 index 000000000..37059db7f --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/DefaultAnalysisEventListener.java @@ -0,0 +1,60 @@ +package com.pig4cloud.pigx.common.excel.handler; + +import com.alibaba.excel.context.AnalysisContext; +import com.pig4cloud.pigx.common.excel.kit.Validators; +import com.pig4cloud.pigx.common.excel.vo.ErrorMessage; +import lombok.extern.slf4j.Slf4j; + +import javax.validation.ConstraintViolation; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 默认的 AnalysisEventListener + * + * @author lengleng + * @author L.cm + * @date 2021/4/16 + */ +@Slf4j +public class DefaultAnalysisEventListener extends ListAnalysisEventListener { + + private final List list = new ArrayList<>(); + + private final List errorMessageList = new ArrayList<>(); + + private Long lineNum = 1L; + + @Override + public void invoke(Object o, AnalysisContext analysisContext) { + lineNum++; + + Set> violations = Validators.validate(o); + if (!violations.isEmpty()) { + Set messageSet = violations.stream().map(ConstraintViolation::getMessage) + .collect(Collectors.toSet()); + errorMessageList.add(new ErrorMessage(lineNum, messageSet)); + } + else { + list.add(o); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + log.debug("Excel read analysed"); + } + + @Override + public List getList() { + return list; + } + + @Override + public List getErrors() { + return errorMessageList; + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/ListAnalysisEventListener.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/ListAnalysisEventListener.java new file mode 100644 index 000000000..822c94ce7 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/ListAnalysisEventListener.java @@ -0,0 +1,27 @@ +package com.pig4cloud.pigx.common.excel.handler; + +import com.alibaba.excel.event.AnalysisEventListener; +import com.pig4cloud.pigx.common.excel.vo.ErrorMessage; + +import java.util.List; + +/** + * list analysis EventListener + * + * @author L.cm + */ +public abstract class ListAnalysisEventListener extends AnalysisEventListener { + + /** + * 获取 excel 解析的对象列表 + * @return 集合 + */ + public abstract List getList(); + + /** + * 获取异常校验结果 + * @return 集合 + */ + public abstract List getErrors(); + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/ManySheetWriteHandler.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/ManySheetWriteHandler.java new file mode 100644 index 000000000..06bd526d1 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/ManySheetWriteHandler.java @@ -0,0 +1,64 @@ +package com.pig4cloud.pigx.common.excel.handler; + +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.annotation.Sheet; +import com.pig4cloud.pigx.common.excel.config.ExcelConfigProperties; +import com.pig4cloud.pigx.common.excel.enhance.WriterBuilderEnhancer; +import com.pig4cloud.pigx.common.excel.kit.ExcelException; +import lombok.SneakyThrows; +import org.springframework.beans.factory.ObjectProvider; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * @author lengleng + * @author L.cm + * @date 2020/3/29 + */ +public class ManySheetWriteHandler extends AbstractSheetWriteHandler { + + public ManySheetWriteHandler(ExcelConfigProperties configProperties, + ObjectProvider>> converterProvider, WriterBuilderEnhancer excelWriterBuilderEnhance) { + super(configProperties, converterProvider, excelWriterBuilderEnhance); + } + + /** + * 当且仅当List不为空且List中的元素也是List 才返回true + * @param obj 返回对象 + * @return + */ + @Override + public boolean support(Object obj) { + if (obj instanceof List) { + List objList = (List) obj; + return !objList.isEmpty() && objList.get(0) instanceof List; + } + else { + throw new ExcelException("@ResponseExcel 返回值必须为List类型"); + } + } + + @Override + @SneakyThrows + public void write(Object obj, HttpServletResponse response, ResponseExcel responseExcel) { + List objList = (List) obj; + ExcelWriter excelWriter = getExcelWriter(response, responseExcel); + + Sheet[] sheets = responseExcel.sheets(); + WriteSheet sheet; + for (int i = 0; i < sheets.length; i++) { + List eleList = (List) objList.get(i); + Class dataClass = eleList.get(0).getClass(); + // 创建sheet + sheet = this.sheet(sheets[i], dataClass, responseExcel.template(), responseExcel.headGenerator()); + // 写入sheet + excelWriter.write(eleList, sheet); + } + excelWriter.finish(); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/SheetWriteHandler.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/SheetWriteHandler.java new file mode 100644 index 000000000..0e4a5625a --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/SheetWriteHandler.java @@ -0,0 +1,44 @@ +package com.pig4cloud.pigx.common.excel.handler; + +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; + +import javax.servlet.http.HttpServletResponse; + +/** + * @author lengleng + * @date 2020/3/29 + *

+ * sheet 写出处理器 + */ +public interface SheetWriteHandler { + + /** + * 是否支持 + * @param obj + * @return + */ + boolean support(Object obj); + + /** + * 校验 + * @param responseExcel 注解 + */ + void check(ResponseExcel responseExcel); + + /** + * 返回的对象 + * @param o obj + * @param response 输出对象 + * @param responseExcel 注解 + */ + void export(Object o, HttpServletResponse response, ResponseExcel responseExcel); + + /** + * 写成对象 + * @param o obj + * @param response 输出对象 + * @param responseExcel 注解 + */ + void write(Object o, HttpServletResponse response, ResponseExcel responseExcel); + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/SingleSheetWriteHandler.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/SingleSheetWriteHandler.java new file mode 100644 index 000000000..d22ebe89a --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/handler/SingleSheetWriteHandler.java @@ -0,0 +1,60 @@ +package com.pig4cloud.pigx.common.excel.handler; + +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; +import com.pig4cloud.pigx.common.excel.config.ExcelConfigProperties; +import com.pig4cloud.pigx.common.excel.enhance.WriterBuilderEnhancer; +import com.pig4cloud.pigx.common.excel.kit.ExcelException; +import lombok.SneakyThrows; +import org.springframework.beans.factory.ObjectProvider; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * @author lengleng + * @author L.cm + * @date 2020/3/29 + *

+ * 处理单sheet 页面 + */ +public class SingleSheetWriteHandler extends AbstractSheetWriteHandler { + + public SingleSheetWriteHandler(ExcelConfigProperties configProperties, + ObjectProvider>> converterProvider, WriterBuilderEnhancer excelWriterBuilderEnhance) { + super(configProperties, converterProvider, excelWriterBuilderEnhance); + } + + /** + * obj 是List 且list不为空同时list中的元素不是是List 才返回true + * @param obj 返回对象 + * @return + */ + @Override + public boolean support(Object obj) { + if (obj instanceof List) { + List objList = (List) obj; + return !objList.isEmpty() && !(objList.get(0) instanceof List); + } + else { + throw new ExcelException("@ResponseExcel 返回值必须为List类型"); + } + } + + @Override + @SneakyThrows + public void write(Object obj, HttpServletResponse response, ResponseExcel responseExcel) { + List list = (List) obj; + ExcelWriter excelWriter = getExcelWriter(response, responseExcel); + + // 有模板则不指定sheet名 + Class dataClass = list.get(0).getClass(); + WriteSheet sheet = this.sheet(responseExcel.sheets()[0], dataClass, responseExcel.template(), + responseExcel.headGenerator()); + excelWriter.write(list, sheet); + excelWriter.finish(); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/HeadGenerator.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/HeadGenerator.java new file mode 100644 index 000000000..8487aec02 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/HeadGenerator.java @@ -0,0 +1,22 @@ +package com.pig4cloud.pigx.common.excel.head; + +/** + * Excel头生成器,用于自定义生成头部信息 + * + * @author Hccake 2020/10/27 + * @version 1.0 + */ +public interface HeadGenerator { + + /** + *

+ * 自定义头部信息 + *

+ * 实现类根据数据的class信息,定制Excel头
+ * 具体方法使用参考:https://www.yuque.com/easyexcel/doc/write#b4b9de00 + * @param clazz 当前sheet的数据类型 + * @return List> Head头信息 + */ + HeadMeta head(Class clazz); + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/HeadMeta.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/HeadMeta.java new file mode 100644 index 000000000..91fff64c5 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/HeadMeta.java @@ -0,0 +1,29 @@ +package com.pig4cloud.pigx.common.excel.head; + +import lombok.Data; + +import java.util.List; +import java.util.Set; + +/** + * @author Yakir + * @date 2021/4/26 10:58 + */ +@Data +public class HeadMeta { + + /** + *

+ * 自定义头部信息 + *

+ * 实现类根据数据的class信息,定制Excel头
+ * 具体方法使用参考:https://www.yuque.com/easyexcel/doc/write#b4b9de00 + */ + private List> head; + + /** + * 忽略头对应字段名称 + */ + private Set ignoreHeadFields; + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/I18nHeaderCellWriteHandler.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/I18nHeaderCellWriteHandler.java new file mode 100644 index 000000000..a0f46d818 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/head/I18nHeaderCellWriteHandler.java @@ -0,0 +1,81 @@ +package com.pig4cloud.pigx.common.excel.head; + +import com.alibaba.excel.metadata.CellData; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.handler.CellWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteTableHolder; +import lombok.RequiredArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.util.PropertyPlaceholderHelper; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 对表头进行国际化处理 + * + * @author hccake + */ +@RequiredArgsConstructor +public class I18nHeaderCellWriteHandler implements CellWriteHandler { + + /** + * 国际化消息源 + */ + private final MessageSource messageSource; + + /** + * 国际化翻译 + */ + private final PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver; + + public I18nHeaderCellWriteHandler(MessageSource messageSource) { + this.messageSource = messageSource; + this.placeholderResolver = placeholderName -> messageSource.getMessage(placeholderName, null, + LocaleContextHolder.getLocale()); + } + + /** + * 占位符处理 + */ + private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("{", "}"); + + @Override + public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, + Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { + if (isHead) { + List originHeadNameList = head.getHeadNameList(); + if (CollectionUtils.isNotEmpty(originHeadNameList)) { + // 国际化处理 + List i18nHeadNames = originHeadNameList.stream() + .map(headName -> propertyPlaceholderHelper.replacePlaceholders(headName, placeholderResolver)) + .collect(Collectors.toList()); + head.setHeadNameList(i18nHeadNames); + } + } + } + + @Override + public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, + Head head, Integer relativeRowIndex, Boolean isHead) { + + } + + @Override + public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + + } + + @Override + public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, + List cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { + + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/kit/ExcelException.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/kit/ExcelException.java new file mode 100644 index 000000000..27f4aecbf --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/kit/ExcelException.java @@ -0,0 +1,15 @@ +package com.pig4cloud.pigx.common.excel.kit; + +/** + * @author lengleng + * @date 2020/3/31 + */ +public class ExcelException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ExcelException(String message) { + super(message); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/kit/Validators.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/kit/Validators.java new file mode 100644 index 000000000..23312b4b2 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/kit/Validators.java @@ -0,0 +1,34 @@ +package com.pig4cloud.pigx.common.excel.kit; + +import javax.validation.*; +import java.util.Set; + +/** + * 校验工具 + * + * @author L.cm + */ +public class Validators { + + private static final Validator validator; + + static { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + /** + * Validates all constraints on {@code object}. + * @param object object to validate + * @param the type of the object to validate + * @return constraint violations or an empty set if none + * @throws IllegalArgumentException if object is {@code null} or if {@code null} is + * passed to the varargs groups + * @throws ValidationException if a non recoverable error happens during the + * validation process + */ + public static Set> validate(T object) { + return validator.validate(object); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/processor/NameProcessor.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/processor/NameProcessor.java new file mode 100644 index 000000000..ab5e3d558 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/processor/NameProcessor.java @@ -0,0 +1,20 @@ +package com.pig4cloud.pigx.common.excel.processor; + +import java.lang.reflect.Method; + +/** + * @author lengleng + * @date 2020/3/29 + */ +public interface NameProcessor { + + /** + * 解析名称 + * @param args 拦截器对象 + * @param method + * @param key 表达式 + * @return + */ + String doDetermineName(Object[] args, Method method, String key); + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/processor/NameSpelExpressionProcessor.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/processor/NameSpelExpressionProcessor.java new file mode 100644 index 000000000..7d4856a9f --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/processor/NameSpelExpressionProcessor.java @@ -0,0 +1,40 @@ +package com.pig4cloud.pigx.common.excel.processor; + +import org.springframework.context.expression.MethodBasedEvaluationContext; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +import java.lang.reflect.Method; + +/** + * @author lengleng + * @date 2020/3/29 + */ +public class NameSpelExpressionProcessor implements NameProcessor { + + /** + * 参数发现器 + */ + private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); + + /** + * Express语法解析器 + */ + private static final ExpressionParser PARSER = new SpelExpressionParser(); + + @Override + public String doDetermineName(Object[] args, Method method, String key) { + + if (!key.contains("#")) { + return key; + } + + EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, NAME_DISCOVERER); + final Object value = PARSER.parseExpression(key).getValue(context); + return value == null ? null : value.toString(); + } + +} diff --git a/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/vo/ErrorMessage.java b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/vo/ErrorMessage.java new file mode 100644 index 000000000..19e562ab8 --- /dev/null +++ b/pigx-common/pigx-common-excel/src/main/java/com/pig4cloud/pigx/common/excel/vo/ErrorMessage.java @@ -0,0 +1,41 @@ +package com.pig4cloud.pigx.common.excel.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashSet; +import java.util.Set; + +/** + * 校验错误信息 + * + * @author lengleng + * @date 2021/8/4 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ErrorMessage { + + /** + * 行号 + */ + private Long lineNum; + + /** + * 错误信息 + */ + private Set errors = new HashSet<>(); + + public ErrorMessage(Set errors) { + this.errors = errors; + } + + public ErrorMessage(String error) { + HashSet objects = new HashSet<>(); + objects.add(error); + this.errors = objects; + } + +}