├── doc ├── main.png ├── viewtask.png ├── schema.sql └── 2023-02-07.sql ├── async-excel-core ├── src │ └── main │ │ └── java │ │ └── com │ │ └── asyncexcel │ │ └── core │ │ ├── ISheetRow.java │ │ ├── ISheetIndex.java │ │ ├── Support.java │ │ ├── TriFunction.java │ │ ├── importer │ │ ├── SheetConst.java │ │ ├── ImportException.java │ │ ├── HeadCheckException.java │ │ ├── DataConvertException.java │ │ ├── MaxRowsLimitException.java │ │ ├── ImportHandler.java │ │ ├── ImportSupport.java │ │ ├── FailMsgWriteHandler.java │ │ ├── DataImportParam.java │ │ ├── HeadContentUtil.java │ │ ├── AsyncExcelImporter.java │ │ ├── ImportContext.java │ │ ├── AsyncPageReadListener.java │ │ └── AsyncImportTaskSupport.java │ │ ├── DataParam.java │ │ ├── service │ │ ├── TaskService.java │ │ └── IStorageService.java │ │ ├── ExportRow.java │ │ ├── annotation │ │ └── ExcelHandle.java │ │ ├── Handler.java │ │ ├── ExceptionUtil.java │ │ ├── ErrorMsg.java │ │ ├── URLUtil.java │ │ ├── exporter │ │ ├── ExportException.java │ │ ├── ExportSupport.java │ │ ├── ExportHandler.java │ │ ├── DataExportParam.java │ │ ├── ExportContext.java │ │ ├── AsyncExcelExporter.java │ │ └── AsyncExportTaskSupport.java │ │ ├── ImportRowMap.java │ │ ├── ExportPage.java │ │ ├── ExcelContext.java │ │ ├── ImportRow.java │ │ └── model │ │ └── ExcelTask.java └── pom.xml ├── async-excel-springboot-starter ├── src │ └── main │ │ └── java │ │ └── com │ │ └── asyncexcel │ │ └── springboot │ │ ├── SpringExcelContext.java │ │ ├── context │ │ ├── mapper │ │ │ └── ExcelTaskMapper.java │ │ ├── service │ │ │ ├── IExcelTaskService.java │ │ │ ├── ExcelTaskServiceImpl.java │ │ │ ├── TaskManage.java │ │ │ ├── ServerLocalStorageService.java │ │ │ └── MockMultipartFile.java │ │ └── mybatisplus │ │ │ ├── ExcelDataSourceProperties.java │ │ │ └── ExcelMybatisPlusConfiguration.java │ │ ├── ExcelHandleBasePackages.java │ │ ├── ExcelThreadPool.java │ │ ├── EnableAsyncExcel.java │ │ ├── ExcelImportSelector.java │ │ ├── DefaultThreadPoolConfiguration.java │ │ ├── ExcelAutoConfiguration.java │ │ ├── ExcelContextConfiguration.java │ │ ├── ExcelHandleBasePackagesRegistrar.java │ │ ├── ExcelContextFactory.java │ │ ├── ExcelContextRegistrar.java │ │ └── ExcelService.java └── pom.xml ├── .gitignore ├── pom.xml ├── README.md └── LICENSE /doc/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2229499815/async-excel/HEAD/doc/main.png -------------------------------------------------------------------------------- /doc/viewtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2229499815/async-excel/HEAD/doc/viewtask.png -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ISheetRow.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | public interface ISheetRow extends ISheetIndex { 4 | 5 | void setRow(int row); 6 | 7 | int getRow(); 8 | } 9 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ISheetIndex.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | public interface ISheetIndex { 4 | 5 | default void setSheetIndex(int sheetIndex) {} 6 | 7 | default int getSheetIndex() { 8 | return 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/Support.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import com.asyncexcel.core.model.ExcelTask; 4 | 5 | /** 6 | * @Description TODO 7 | * @Author 姚仲杰#80998699 8 | * @Date 2022/8/25 14:40 9 | */ 10 | public interface Support { 11 | } 12 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/TriFunction.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/10/18 17:25 7 | */ 8 | @FunctionalInterface 9 | public interface TriFunction { 10 | R apply(S s, T t, U u); 11 | } 12 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/SheetConst.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/10/26 10:34 7 | */ 8 | public class SheetConst { 9 | 10 | public final static String FAIL_MSG_TITLE="失败原因"; 11 | public final static String FAIL_ROW_TITLE="原文件行号"; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/SpringExcelContext.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/7/7 16:46 7 | */ 8 | public class SpringExcelContext extends ExcelContextFactory { 9 | 10 | public SpringExcelContext() { 11 | super(ExcelContextConfiguration.class, "excelContext", "spring.excel.name"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/DataParam.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import java.util.Map; 4 | import lombok.Data; 5 | 6 | /** 7 | * @Description TODO 8 | * @Author 姚仲杰#80998699 9 | * @Date 2022/8/25 14:40 10 | */ 11 | @Data 12 | public class DataParam { 13 | private Map parameters; 14 | private String tenantCode; 15 | private String createUserCode; 16 | private String businessCode; 17 | } 18 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/mapper/ExcelTaskMapper.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.mapper; 2 | 3 | import com.asyncexcel.core.model.ExcelTask; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 导入导出任务 Mapper 接口 9 | *

10 | * 11 | * @author 姚仲杰 12 | * @since 2022-07-05 13 | */ 14 | public interface ExcelTaskMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/service/IExcelTaskService.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.service; 2 | 3 | import com.asyncexcel.core.model.ExcelTask; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | 7 | /** 8 | *

9 | * 导入导出任务 服务类 10 | *

11 | * 12 | * @author 姚仲杰 13 | * @since 2022-07-05 14 | */ 15 | public interface IExcelTaskService extends IService { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/service/TaskService.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.service; 2 | 3 | import com.asyncexcel.core.model.ExcelTask; 4 | 5 | /** 6 | * @Description TODO 7 | * @Author 姚仲杰#80998699 8 | * @Date 2022/8/25 14:34 9 | */ 10 | public interface TaskService { 11 | 12 | /**保存任务 13 | * @param task 14 | * @return 15 | */ 16 | boolean save(ExcelTask task); 17 | 18 | /**根据id更新任务 19 | * @param task 20 | * @return 21 | */ 22 | boolean updateById(ExcelTask task); 23 | } 24 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ExportRow.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import com.alibaba.excel.annotation.ExcelIgnore; 4 | 5 | /** 6 | * @Description TODO 7 | * @Author 姚仲杰#80998699 8 | * @Date 2022/7/18 16:09 9 | */ 10 | public class ExportRow implements ISheetRow { 11 | 12 | @ExcelIgnore 13 | private int row; 14 | 15 | @Override 16 | public int getRow() { 17 | return row; 18 | } 19 | 20 | @Override 21 | public void setRow(int row) { 22 | this.row = row; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelHandleBasePackages.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * @Description TODO 7 | * @Author 姚仲杰#80998699 8 | * @Date 2022/7/7 23:10 9 | */ 10 | public class ExcelHandleBasePackages { 11 | private Set basePackages; 12 | 13 | public ExcelHandleBasePackages(Set basePackages){ 14 | this.basePackages=basePackages; 15 | } 16 | 17 | public Set getBasePackages(){ 18 | return basePackages; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/annotation/ExcelHandle.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * @Description 注解在开发自定义的导入导出逻辑中,会通过扫描作为bean被注册到子容器中 11 | * @Author 姚仲杰#80998699 12 | * @Date 2022/7/7 16:38 13 | */ 14 | @Target(ElementType.TYPE) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | public @interface ExcelHandle { 18 | String name() default ""; 19 | } 20 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/service/ExcelTaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.service; 2 | 3 | import com.asyncexcel.core.model.ExcelTask; 4 | import com.asyncexcel.springboot.context.mapper.ExcelTaskMapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | *

10 | * 导入导出任务 服务实现类 11 | *

12 | * 13 | * @author 姚仲杰 14 | * @since 2022-07-05 15 | */ 16 | @Service 17 | public class ExcelTaskServiceImpl extends ServiceImpl implements 18 | IExcelTaskService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/Handler.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/8/25 14:40 7 | */ 8 | public interface Handler { 9 | 10 | /** 11 | * 整个生命周期中只会执行一次该方法,可以用于初始化处理类的一些全局属性 12 | * 20221021 添加允许修改param 13 | * @param ctx 14 | */ 15 | default void init(ExcelContext ctx, DataParam param) { 16 | //do some init operation 17 | } 18 | 19 | /** 整个生命周期完成后回调处理 20 | * @param ctx 21 | * @param param 22 | */ 23 | default void callBack(ExcelContext ctx, DataParam param){ 24 | //do something callBack 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # maven ignore 26 | target/ 27 | .mvn/ 28 | bin/ 29 | .sts4-cache/ 30 | *.versionsBackup 31 | 32 | # eclipse ignore 33 | .settings/ 34 | .project 35 | .classpath 36 | .factorypath 37 | 38 | # idea ignore 39 | .idea/ 40 | *.ipr 41 | *.iml 42 | *.iws 43 | 44 | # system ignore 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelThreadPool.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | 5 | /** 6 | * @Description TODO 7 | * @Author 姚仲杰#80998699 8 | * @Date 2022/8/24 13:55 9 | */ 10 | public class ExcelThreadPool { 11 | private ExecutorService executor; 12 | 13 | public ExcelThreadPool(ExecutorService executor) { 14 | this.executor = executor; 15 | } 16 | 17 | public ExecutorService getExecutor() { 18 | return executor; 19 | } 20 | 21 | public void setExecutor(ExecutorService executor) { 22 | this.executor = executor; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | /** 4 | * @Description 异常转换 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/7/13 20:26 7 | */ 8 | public class ExceptionUtil { 9 | 10 | public static RuntimeException wrap2Runtime(Exception e) { 11 | if (e instanceof RuntimeException) { 12 | return (RuntimeException) e; 13 | } 14 | return new RuntimeException(e); 15 | } 16 | 17 | public static Throwable getOriginal(Throwable e){ 18 | Throwable ex = e.getCause(); 19 | if(ex != null){ 20 | return getOriginal(ex); 21 | }else{ 22 | return e; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ErrorMsg.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/7/5 10:26 7 | */ 8 | public class ErrorMsg { 9 | private Integer row; 10 | private String msg; 11 | 12 | public ErrorMsg(Integer row, String msg) { 13 | this.row = row; 14 | this.msg = msg; 15 | } 16 | 17 | public Integer getRow() { 18 | return row; 19 | } 20 | 21 | public void setRow(Integer row) { 22 | this.row = row; 23 | } 24 | 25 | public String getMsg() { 26 | return msg; 27 | } 28 | 29 | public void setMsg(String msg) { 30 | this.msg = msg; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/EnableAsyncExcel.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import org.springframework.context.annotation.Import; 9 | 10 | /** 11 | * @Description TODO 12 | * @Author 姚仲杰#80998699 13 | * @Date 2022/7/5 17:20 14 | */ 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | @Import({ExcelImportSelector.class,ExcelHandleBasePackagesRegistrar.class}) 19 | public @interface EnableAsyncExcel { 20 | String[] basePackages() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/URLUtil.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | 8 | public class URLUtil { 9 | 10 | public static InputStream getStream(URL url) { 11 | try { 12 | return url.openStream(); 13 | } catch (IOException var2) { 14 | throw new RuntimeException(var2); 15 | } 16 | } 17 | 18 | public static InputStream getStream(String url){ 19 | try { 20 | URL fileUrl = new URL(url); 21 | return URLUtil.getStream(fileUrl); 22 | } catch (MalformedURLException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/service/TaskManage.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.service; 2 | 3 | import com.asyncexcel.core.model.ExcelTask; 4 | import com.asyncexcel.core.service.TaskService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * @Description TODO 10 | * @Author 姚仲杰#80998699 11 | * @Date 2022/8/25 18:45 12 | */ 13 | @Service 14 | public class TaskManage implements TaskService { 15 | 16 | @Autowired 17 | IExcelTaskService excelTaskService; 18 | @Override 19 | public boolean save(ExcelTask task) { 20 | return excelTaskService.save(task); 21 | } 22 | 23 | @Override 24 | public boolean updateById(ExcelTask task) { 25 | return excelTaskService.updateById(task); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/ExportException.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | /** 4 | * @Description 导出异常 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/8/2 10:08 7 | */ 8 | public class ExportException extends RuntimeException { 9 | 10 | public ExportException() { 11 | super(); 12 | } 13 | 14 | public ExportException(String message) { 15 | super(message); 16 | } 17 | 18 | public ExportException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public ExportException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected ExportException(String message, Throwable cause, boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/ImportException.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/8/2 10:10 7 | */ 8 | public class ImportException extends RuntimeException { 9 | 10 | public ImportException() { 11 | super(); 12 | } 13 | 14 | public ImportException(String message) { 15 | super(message); 16 | } 17 | 18 | public ImportException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public ImportException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected ImportException(String message, Throwable cause, boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/HeadCheckException.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/7/27 16:12 7 | */ 8 | public class HeadCheckException extends ImportException { 9 | 10 | public HeadCheckException() { 11 | super(); 12 | } 13 | 14 | public HeadCheckException(String message) { 15 | super(message); 16 | } 17 | 18 | public HeadCheckException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public HeadCheckException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected HeadCheckException(String message, Throwable cause, boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/DataConvertException.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/8/22 16:28 7 | */ 8 | public class DataConvertException extends ImportException { 9 | 10 | public DataConvertException() { 11 | super(); 12 | } 13 | 14 | public DataConvertException(String message) { 15 | super(message); 16 | } 17 | 18 | public DataConvertException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public DataConvertException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected DataConvertException(String message, Throwable cause, boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/MaxRowsLimitException.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | /** 4 | * @Description TODO 5 | * @Author 姚仲杰#80998699 6 | * @Date 2022/7/26 16:22 7 | */ 8 | public class MaxRowsLimitException extends ImportException { 9 | 10 | public MaxRowsLimitException() { 11 | super(); 12 | } 13 | 14 | public MaxRowsLimitException(String message) { 15 | super(message); 16 | } 17 | 18 | public MaxRowsLimitException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public MaxRowsLimitException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | protected MaxRowsLimitException(String message, Throwable cause, boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/ExportSupport.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | import com.asyncexcel.core.Support; 4 | import com.asyncexcel.core.model.ExcelTask; 5 | import java.util.Collection; 6 | 7 | /** 8 | * @Description TODO 9 | * @Author 姚仲杰#80998699 10 | * @Date 2022/7/14 19:40 11 | */ 12 | public interface ExportSupport extends Support { 13 | 14 | /**创建任务 15 | * @param param 16 | * @return 17 | */ 18 | ExcelTask createTask(DataExportParam param); 19 | 20 | /** 导出阶段 21 | * @param ctx 22 | */ 23 | void onExport(ExportContext ctx); 24 | 25 | /** 写文件阶段 26 | * @param dataList 27 | * @param ctx 28 | */ 29 | void onWrite(Collection dataList, ExportContext ctx); 30 | 31 | /**完成阶段 32 | * @param ctx 33 | */ 34 | void onComplete(ExportContext ctx); 35 | 36 | /**失败处理阶段 37 | * @param ctx 38 | */ 39 | void onError(ExportContext ctx); 40 | } 41 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ImportRowMap.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import com.alibaba.excel.annotation.ExcelIgnore; 4 | import com.alibaba.excel.metadata.Cell; 5 | import com.alibaba.excel.metadata.data.ReadCellData; 6 | import java.util.Map; 7 | 8 | /** 9 | * @Description TODO 10 | * @Author 姚仲杰#80998699 11 | * @Date 2022/10/25 15:48 12 | */ 13 | public class ImportRowMap extends ImportRow { 14 | 15 | @ExcelIgnore 16 | private Map headMap; 17 | 18 | @ExcelIgnore 19 | private Map dataMap; 20 | 21 | public Map getHeadMap() { 22 | return headMap; 23 | } 24 | 25 | public void setHeadMap(Map headMap) { 26 | this.headMap = headMap; 27 | } 28 | 29 | public MapgetDataMap() { 30 | return dataMap; 31 | } 32 | 33 | public void setDataMap(Map dataMap) { 34 | this.dataMap = dataMap; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/ExportHandler.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | import com.asyncexcel.core.ExportPage; 4 | import com.asyncexcel.core.Handler; 5 | import java.util.List; 6 | 7 | /** 8 | * @Description TODO 9 | * @Author 姚仲杰#80998699 10 | * @Date 2022/7/7 23:40 11 | */ 12 | public interface ExportHandler extends Handler { 13 | 14 | /**分页导入导出 15 | * @param startPage 其实页 16 | * @param limit 每页限制大小 17 | * @param param 外部入参 18 | * @return 返回要导出的数据 19 | */ 20 | ExportPage exportData(int startPage, int limit, DataExportParam param); 21 | 22 | /**每页开始前在exportData执行前执行 23 | * @param ctx 导出上下文,你可以在开始前进行修改 24 | * @param param 导出参数 25 | */ 26 | default void beforePerPage(ExportContext ctx, DataExportParam param) { 27 | } 28 | 29 | /**exportData执行后 30 | * @param list 得到的数据 31 | * @param ctx 上下文 32 | * @param param 外部入参 33 | */ 34 | default void afterPerPage(List list, ExportContext ctx, DataExportParam param) { 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelImportSelector.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import org.springframework.context.annotation.DeferredImportSelector; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.core.type.AnnotationMetadata; 8 | 9 | /** 10 | * @Description 收集注册给主容器的bean,此处使用deferredImportSelector目的是为了设置顺序晚于Springboot的 11 | * spring.factories加载。 12 | * @Author 姚仲杰#80998699 13 | * @Date 2022/7/7 16:53 14 | */ 15 | public class ExcelImportSelector implements DeferredImportSelector, Ordered { 16 | 17 | @Override 18 | public String[] selectImports(AnnotationMetadata importingClassMetadata) { 19 | List importList=new ArrayList(); 20 | importList.add(ExcelAutoConfiguration.class.getName()); 21 | importList.add(ExcelService.class.getName()); 22 | return importList.toArray(new String[importList.size()]); 23 | } 24 | 25 | @Override 26 | public int getOrder() { 27 | return Ordered.LOWEST_PRECEDENCE; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/ImportHandler.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.asyncexcel.core.ErrorMsg; 4 | import com.asyncexcel.core.Handler; 5 | import java.util.List; 6 | 7 | 8 | /** 9 | * @Description TODO 10 | * @Author 姚仲杰#80998699 11 | * @Date 2022/7/5 14:14 12 | */ 13 | public interface ImportHandler extends Handler { 14 | 15 | /**导入数据 16 | * @param list 17 | * @param param 18 | * @return 19 | * @throws Exception 20 | */ 21 | List importData(List list, DataImportParam param) throws Exception; 22 | 23 | /**导入前 24 | * @param list 25 | * @param param 26 | * @throws Exception 27 | */ 28 | default void beforePerPage(ImportContext ctx, List list, DataImportParam param) throws Exception { 29 | } 30 | 31 | /**导入后 32 | * @param list 33 | * @param param 34 | * @param errorMsgList 35 | * @throws Exception 36 | */ 37 | default void afterPerPage(ImportContext ctx, List list, DataImportParam param, List errorMsgList) 38 | throws Exception { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ExportPage.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * @Description 导出分页 8 | * @Author 姚仲杰#80998699 9 | * @Date 2022/7/18 11:37 10 | */ 11 | public class ExportPage { 12 | private Long total; 13 | private Long size; 14 | private Long current; 15 | 16 | List records=new ArrayList<>(); 17 | 18 | public List getRecords() { 19 | return records; 20 | } 21 | 22 | public void setRecords(List records) { 23 | this.records = records; 24 | } 25 | 26 | public Long getTotal() { 27 | return total; 28 | } 29 | 30 | public void setTotal(Long total) { 31 | this.total = total; 32 | } 33 | 34 | public Long getSize() { 35 | return size; 36 | } 37 | 38 | public void setSize(Long size) { 39 | this.size = size; 40 | } 41 | 42 | public Long getCurrent() { 43 | return current; 44 | } 45 | 46 | public void setCurrent(Long current) { 47 | this.current = current; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/service/IStorageService.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.service; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * @Description TODO 9 | * @Author 姚仲杰#80998699 10 | * @Date 2022/8/25 14:34 11 | */ 12 | public interface IStorageService { 13 | /**自定义一个输出流函数写出去 14 | * @param name 15 | * @param osConsumer 16 | * @return 文件路径 17 | * @throws Exception 18 | */ 19 | String write(String name, Consumer osConsumer) throws Exception; 20 | 21 | /**从一个输入流读取数据写出到另一个数据流中去 22 | * @param name 文件名 23 | * @param data 数据流 24 | * @return 文件路径 25 | * @throws Exception 26 | */ 27 | String write(String name, InputStream data) throws Exception; 28 | 29 | 30 | /** 读文件 31 | * @param path 32 | * @return 33 | * @throws Exception 34 | */ 35 | InputStream read(String path) throws Exception; 36 | 37 | 38 | /**删除文件 39 | * @param path 40 | * @return 41 | * @throws Exception 42 | */ 43 | boolean delete(String path) throws Exception; 44 | } 45 | -------------------------------------------------------------------------------- /doc/schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists excel_task; 2 | CREATE TABLE `excel_task` ( 3 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', 4 | `type` tinyint(2) NOT NULL COMMENT '类型:1-导入,2-导出', 5 | `status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '状态:0-初始,1-进行中,2-完成,3-失败', 6 | `estimate_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '预估总记录数', 7 | `total_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '实际总记录数', 8 | `success_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '成功记录数', 9 | `failed_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '失败记录数', 10 | `file_name` varchar(200) DEFAULT NULL COMMENT '文件名', 11 | `file_url` varchar(500) DEFAULT NULL COMMENT '文件路径', 12 | `failed_file_url` varchar(500) DEFAULT NULL COMMENT '失败文件路径', 13 | `failed_message` varchar(255) DEFAULT NULL COMMENT '失败消息', 14 | `start_time` datetime DEFAULT NULL COMMENT '开始时间', 15 | `end_time` datetime DEFAULT NULL COMMENT '结束时间', 16 | `tenant_code` varchar(50) default NULL COMMENT '租户编码', 17 | `create_user_code` varchar(50) default NULL COMMENT '用户编码', 18 | `business_code` varchar(50) default NULL COMMENT '业务编码', 19 | PRIMARY KEY (`id`) 20 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='导入导出任务'; 21 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/ImportSupport.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.asyncexcel.core.ErrorMsg; 4 | import com.asyncexcel.core.Support; 5 | import com.asyncexcel.core.model.ExcelTask; 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | /** 10 | * @Description TODO 11 | * @Author 姚仲杰#80998699 12 | * @Date 2022/7/13 15:29 13 | */ 14 | public interface ImportSupport extends Support { 15 | 16 | /**创建任务 17 | * @param param 18 | * @return 19 | */ 20 | ExcelTask createTask(DataImportParam param); 21 | 22 | /** 23 | * 批次导入前 24 | */ 25 | void beforeImport(); 26 | 27 | /**开始执行导入 28 | * @param ctx 29 | */ 30 | void onImport(ImportContext ctx); 31 | 32 | /**正常数据错误格式错误等写入错误文件返回下载链接 33 | * @param dataList 34 | * @param ctx 35 | * @param errorMsgList 36 | */ 37 | void onWrite(Collection dataList, ImportContext ctx, List errorMsgList); 38 | 39 | /**失败阶段由于某写原因导致导入中断,如业务抛异常了,或者服务中断了 40 | * @param ctx 41 | */ 42 | void onError(ImportContext ctx); 43 | 44 | /**王成阶段 45 | * @param ctx 46 | */ 47 | void onComplete(ImportContext ctx); 48 | } 49 | -------------------------------------------------------------------------------- /doc/2023-02-07.sql: -------------------------------------------------------------------------------- 1 | 2 | -- 升级执行 3 | alter table excel_task add column source_file varchar(1024) comment '源文件' after `status`; 4 | 5 | 6 | -- 新创建执行 7 | drop table if exists excel_task; 8 | CREATE TABLE `excel_task` ( 9 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', 10 | `type` tinyint(2) NOT NULL COMMENT '类型:1-导入,2-导出', 11 | `status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '状态:0-初始,1-进行中,2-完成,3-失败', 12 | `source_file` varchar(1024) comment '源文件', 13 | `estimate_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '预估总记录数', 14 | `total_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '实际总记录数', 15 | `success_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '成功记录数', 16 | `failed_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '失败记录数', 17 | `file_name` varchar(200) DEFAULT NULL COMMENT '文件名', 18 | `file_url` varchar(500) DEFAULT NULL COMMENT '文件路径', 19 | `failed_file_url` varchar(500) DEFAULT NULL COMMENT '失败文件路径', 20 | `failed_message` varchar(255) DEFAULT NULL COMMENT '失败消息', 21 | `start_time` datetime DEFAULT NULL COMMENT '开始时间', 22 | `end_time` datetime DEFAULT NULL COMMENT '结束时间', 23 | `tenant_code` varchar(50) default NULL COMMENT '租户编码', 24 | `create_user_code` varchar(50) default NULL COMMENT '用户编码', 25 | `business_code` varchar(50) default NULL COMMENT '业务编码', 26 | PRIMARY KEY (`id`) 27 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='导入导出任务'; 28 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/mybatisplus/ExcelDataSourceProperties.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.mybatisplus; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | /** 6 | * @Description TODO 7 | * @Author 姚仲杰#80998699 8 | * @Date 2022/7/5 16:45 9 | */ 10 | @ConfigurationProperties("spring.excel.datasource") 11 | public class ExcelDataSourceProperties { 12 | 13 | private String driverClassName; 14 | private String url; 15 | private String username; 16 | private String password; 17 | 18 | public String getDriverClassName() { 19 | return driverClassName; 20 | } 21 | 22 | public void setDriverClassName(String driverClassName) { 23 | this.driverClassName = driverClassName; 24 | } 25 | 26 | public String getUrl() { 27 | return url; 28 | } 29 | 30 | public void setUrl(String url) { 31 | this.url = url; 32 | } 33 | 34 | public String getUsername() { 35 | return username; 36 | } 37 | 38 | public void setUsername(String username) { 39 | this.username = username; 40 | } 41 | 42 | public String getPassword() { 43 | return password; 44 | } 45 | 46 | public void setPassword(String password) { 47 | this.password = password; 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/DefaultThreadPoolConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * @Description 默认线程池实现 12 | * @Author 姚仲杰#80998699 13 | * @Date 2022/8/25 10:35 14 | */ 15 | @Configuration 16 | public class DefaultThreadPoolConfiguration { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | public ExcelThreadPool excelThreadPool(){ 21 | int processors = Runtime.getRuntime().availableProcessors(); 22 | int coreSize=2; 23 | int maxSize=4; 24 | if (processors>1){ 25 | coreSize=2*processors-1; 26 | maxSize=4*processors-1; 27 | } 28 | ThreadPoolExecutor executor = new ThreadPoolExecutor( 29 | coreSize, 30 | maxSize, 31 | 0, 32 | TimeUnit.SECONDS, 33 | new ArrayBlockingQueue<>(20) 34 | ); 35 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 36 | executor.shutdownNow(); 37 | })); 38 | return new ExcelThreadPool(executor); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/FailMsgWriteHandler.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.alibaba.excel.write.handler.context.CellWriteHandlerContext; 4 | import com.alibaba.excel.write.style.AbstractCellStyleStrategy; 5 | import org.apache.poi.ss.usermodel.CellStyle; 6 | import org.apache.poi.ss.usermodel.Font; 7 | import org.apache.poi.ss.usermodel.Workbook; 8 | 9 | /** 10 | * @Description TODO 11 | * @Author 姚仲杰#80998699 12 | * @Date 2022/10/26 10:02 13 | */ 14 | public class FailMsgWriteHandler extends AbstractCellStyleStrategy { 15 | 16 | 17 | @Override 18 | protected void setHeadCellStyle(CellWriteHandlerContext context) { 19 | } 20 | 21 | @Override 22 | protected void setContentCellStyle(CellWriteHandlerContext context) { 23 | String title=context.getHeadData().getHeadNameList().get(0); 24 | if (SheetConst.FAIL_MSG_TITLE.equals(title)||SheetConst.FAIL_ROW_TITLE.equals(title)){ 25 | Workbook workbook = context.getCell().getSheet().getWorkbook(); 26 | CellStyle cellStyle = workbook.createCellStyle(); 27 | Font font = workbook.createFont(); 28 | font.setFontName("宋体"); 29 | font.setFontHeightInPoints((short) 14); 30 | font.setBold(true); 31 | font.setColor(Font.COLOR_RED); 32 | cellStyle.setFont(font); 33 | context.getCell().setCellStyle(cellStyle); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/DataImportParam.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.asyncexcel.core.DataParam; 4 | import java.io.InputStream; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.NoArgsConstructor; 8 | import lombok.ToString; 9 | import lombok.experimental.Accessors; 10 | 11 | /** 12 | * @Description TODO 13 | * @Author 姚仲杰#80998699 14 | * @Date 2022/7/5 15:14 15 | */ 16 | @Data 17 | @NoArgsConstructor 18 | @EqualsAndHashCode(callSuper = true) 19 | @ToString(callSuper = true) 20 | @Accessors(chain = true) 21 | public class DataImportParam extends DataParam { 22 | 23 | /** 24 | * 输入流 25 | */ 26 | private InputStream stream; 27 | 28 | /** 29 | * 源文件url,用于保存源文件地址 30 | */ 31 | private String sourceFile; 32 | /** 33 | * 文件名称 34 | */ 35 | private String filename; 36 | /** 37 | * 导入对应的实体类 38 | */ 39 | private Class model; 40 | /** 41 | * 分批次大小,如果你导入1w条数据,每次1000会分10次读到内存中 42 | */ 43 | private int batchSize = 1000; 44 | 45 | /** 46 | * 是否限制导入行数,默认false,如果限制行数将会出发行数限制异常,例如限制1000行,你的文件如果超过1000行将会抛异常 47 | */ 48 | private boolean validMaxRows = false; 49 | 50 | /** 51 | * 行数限制validMaxRows=true时起作用 52 | */ 53 | private int maxRows = 1000; 54 | 55 | /** 56 | * 是否进行表头校验,顺序单元格内容都应该与实体类保持一致。 57 | */ 58 | private boolean validHead = true; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ExcelContext.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | 4 | import com.asyncexcel.core.model.ExcelTask; 5 | 6 | /** 7 | * @Description 导入导出上下文 8 | * @Author 姚仲杰#80998699 9 | * @Date 2022/7/15 14:42 10 | */ 11 | public class ExcelContext { 12 | private ExcelTask task; 13 | private Long totalCount=0L; 14 | private Long failCount=0L; 15 | private Long successCount=0L; 16 | 17 | public ExcelTask getTask() { 18 | return task; 19 | } 20 | 21 | public void setTask(ExcelTask task) { 22 | this.task = task; 23 | } 24 | 25 | public void record(int dataSize){ 26 | record(dataSize,0); 27 | } 28 | 29 | public void record(int dataSize,int errorSize){ 30 | this.totalCount=this.totalCount+dataSize; 31 | this.successCount=this.successCount+dataSize-errorSize; 32 | this.failCount=this.failCount+errorSize; 33 | } 34 | 35 | public Long getTotalCount() { 36 | return totalCount; 37 | } 38 | 39 | public void setTotalCount(Long totalCount) { 40 | this.totalCount = totalCount; 41 | } 42 | 43 | public Long getFailCount() { 44 | return failCount; 45 | } 46 | 47 | public void setFailCount(Long failCount) { 48 | this.failCount = failCount; 49 | } 50 | 51 | public Long getSuccessCount() { 52 | return successCount; 53 | } 54 | 55 | public void setSuccessCount(Long successCount) { 56 | this.successCount = successCount; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/DataExportParam.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | import com.alibaba.excel.converters.Converter; 4 | import com.alibaba.excel.write.handler.WriteHandler; 5 | import com.asyncexcel.core.DataParam; 6 | import java.util.List; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.NoArgsConstructor; 10 | import lombok.ToString; 11 | import lombok.experimental.Accessors; 12 | 13 | /** 14 | * @Description TODO 15 | * @Author 姚仲杰#80998699 16 | * @Date 2022/7/5 15:14 17 | */ 18 | @Data 19 | @NoArgsConstructor 20 | @EqualsAndHashCode(callSuper = true) 21 | @ToString(callSuper = true) 22 | @Accessors(chain = true) 23 | public class DataExportParam extends DataParam { 24 | 25 | /** 26 | * 分页大小 27 | */ 28 | private int limit=1000; 29 | /** 30 | * 导出文件名称 31 | */ 32 | private String exportFileName; 33 | /** 34 | * 写入excel的sheetName 35 | */ 36 | @Deprecated 37 | private String sheetName; 38 | /** 39 | * 是否动态表头,默认false。 40 | */ 41 | @Deprecated 42 | private boolean dynamicHead; 43 | /** 44 | * 当dynamicHead=true时需要传一个动态表头进来 45 | */ 46 | @Deprecated 47 | private List> headList; 48 | /** 49 | * 表头对应的实体类 50 | */ 51 | @Deprecated 52 | private Class headClass; 53 | /** 54 | * 自定义写处理器为了,自定义样式,表格合并之类的easyExcel原生扩展 55 | */ 56 | @Deprecated 57 | private List writeHandlers; 58 | /** 59 | * 自定义类型转换器easyExcel原生扩展 60 | */ 61 | @Deprecated 62 | private List> converters; 63 | } 64 | -------------------------------------------------------------------------------- /async-excel-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | async-excel 7 | com.asyncexcel 8 | 1.1.2 9 | 10 | 4.0.0 11 | jar 12 | async-excel-core 13 | async-excel-core 14 | https://github.com/2229499815/async-excel.git 15 | AsyncExcel base on easy-excel for async 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-compiler-plugin 21 | 3.8.1 22 | 23 | 8 24 | 8 25 | UTF-8 26 | true 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-resources-plugin 32 | 2.4 33 | 34 | UTF-8 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | 11 | /** 12 | * @Description 懒加载子容器,直到调用时才会初始化容器,可能会造成错误无法预知,在下一个版本将会改成先加载 13 | * @Author 姚仲杰#80998699 14 | * @Date 2022/7/7 16:51 15 | */ 16 | @Configuration 17 | public class ExcelAutoConfiguration { 18 | 19 | @Bean 20 | public SpringExcelContext springExcelContext(){ 21 | SpringExcelContext context = new SpringExcelContext(); 22 | return context; 23 | } 24 | 25 | @Bean 26 | public ExcelService excelService(SpringExcelContext springExcelContext,ExcelThreadPool excelThreadPool){ 27 | return new ExcelService(excelThreadPool,springExcelContext); 28 | } 29 | 30 | @Bean 31 | @ConditionalOnMissingBean 32 | public ExcelThreadPool excelThreadPool(){ 33 | int processors = Runtime.getRuntime().availableProcessors(); 34 | int coreSize=2; 35 | int maxSize=4; 36 | if (processors>1){ 37 | coreSize=2*processors-1; 38 | maxSize=4*processors-1; 39 | } 40 | ThreadPoolExecutor executor = new ThreadPoolExecutor( 41 | coreSize, 42 | maxSize, 43 | 0, 44 | TimeUnit.SECONDS, 45 | new ArrayBlockingQueue<>(20) 46 | ); 47 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 48 | executor.shutdownNow(); 49 | })); 50 | return new ExcelThreadPool(executor); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/service/ServerLocalStorageService.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.service; 2 | 3 | import com.asyncexcel.core.service.IStorageService; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * @Description TODO 12 | * @Author 姚仲杰#80998699 13 | * @Date 2022/8/25 16:49 14 | */ 15 | public class ServerLocalStorageService implements IStorageService { 16 | private final static String tempPath="/tmp/upload/"; 17 | 18 | @Override 19 | public String write(String name, Consumer osConsumer) throws Exception { 20 | return null; 21 | } 22 | 23 | @Override 24 | public String write(String name, InputStream data) throws Exception { 25 | String filePath = tempPath + name; 26 | MockMultipartFile mockMultipartFile = new MockMultipartFile(name, data); 27 | File file =new File(filePath); 28 | if (null == file) { 29 | return null; 30 | } else { 31 | if (!file.exists()) { 32 | File parentFile = file.getParentFile(); 33 | if (!parentFile.exists()) { 34 | parentFile.mkdirs(); 35 | } 36 | try { 37 | file.createNewFile(); 38 | } catch (Exception var2) { 39 | throw new IOException(var2); 40 | } 41 | } 42 | } 43 | mockMultipartFile.transferTo(file); 44 | return filePath; 45 | } 46 | 47 | @Override 48 | public InputStream read(String path) throws Exception { 49 | return null; 50 | } 51 | 52 | @Override 53 | public boolean delete(String path) throws Exception { 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/ImportRow.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core; 2 | 3 | import com.alibaba.excel.annotation.ExcelIgnore; 4 | import com.alibaba.excel.annotation.ExcelProperty; 5 | import com.alibaba.excel.annotation.write.style.ContentFontStyle; 6 | import com.asyncexcel.core.importer.SheetConst; 7 | import java.util.Objects; 8 | import org.apache.poi.ss.usermodel.Font; 9 | 10 | /** 11 | * 所有excel实体继承这个实体 12 | * @author: 姚仲杰 13 | * @since 2022-07-012 14 | */ 15 | public class ImportRow implements ISheetRow { 16 | 17 | @ContentFontStyle(color = Font.COLOR_RED) 18 | @ExcelProperty(SheetConst.FAIL_MSG_TITLE) 19 | String rowFailMessage; 20 | @ExcelIgnore 21 | private int sheetIndex; 22 | @ContentFontStyle(color = Font.COLOR_RED) 23 | @ExcelProperty(SheetConst.FAIL_ROW_TITLE) 24 | private int row; 25 | 26 | public String getRowFailMessage() { 27 | return rowFailMessage; 28 | } 29 | 30 | public void setRowFailMessage(String rowFailMessage) { 31 | this.rowFailMessage = rowFailMessage; 32 | } 33 | 34 | @Override 35 | public int getSheetIndex() { 36 | return sheetIndex; 37 | } 38 | @Override 39 | public void setSheetIndex(int sheetIndex) { 40 | this.sheetIndex = sheetIndex; 41 | } 42 | @Override 43 | public int getRow() { 44 | return row; 45 | } 46 | @Override 47 | public void setRow(int row) { 48 | this.row = row; 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o == null || getClass() != o.getClass()) { 57 | return false; 58 | } 59 | ImportRow row1 = (ImportRow) o; 60 | return sheetIndex == row1.sheetIndex && row == row1.row; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hash(sheetIndex, row); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import com.asyncexcel.core.exporter.AsyncExportTaskSupport; 4 | import com.asyncexcel.core.exporter.ExportSupport; 5 | import com.asyncexcel.core.importer.AsyncImportTaskSupport; 6 | import com.asyncexcel.core.importer.ImportSupport; 7 | import com.asyncexcel.core.service.IStorageService; 8 | import com.asyncexcel.core.service.TaskService; 9 | import com.asyncexcel.springboot.context.service.ServerLocalStorageService; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.ComponentScan; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Import; 16 | 17 | /** 18 | * @Description 注册给子容器的bean通过ComponentScan 与 自定义@ExcelHandle注解扫描写在主项目中的扩展bean 19 | * @Author 姚仲杰#80998699 20 | * @Date 2022/7/6 15:18 21 | */ 22 | @Configuration 23 | @ComponentScan({"com.asyncexcel.springboot.context"}) 24 | @Import(ExcelContextRegistrar.class) 25 | public class ExcelContextConfiguration{ 26 | 27 | 28 | /**暴露给外部扩展 29 | * @return 30 | */ 31 | @Bean 32 | @ConditionalOnMissingBean(IStorageService.class) 33 | IStorageService storageService(){ 34 | return new ServerLocalStorageService(); 35 | } 36 | 37 | 38 | @Bean 39 | @ConditionalOnBean({IStorageService.class, TaskService.class}) 40 | ImportSupport importSupport(TaskService taskService,IStorageService storageService){ 41 | return new AsyncImportTaskSupport(storageService,taskService); 42 | } 43 | 44 | 45 | @Bean 46 | @ConditionalOnBean({IStorageService.class,TaskService.class}) 47 | ExportSupport exportSupport(TaskService taskService,IStorageService storageService){ 48 | return new AsyncExportTaskSupport(storageService,taskService); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/model/ExcelTask.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.model; 2 | 3 | 4 | import java.io.Serializable; 5 | import java.time.LocalDateTime; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | /** 11 | *

12 | * 导入导出任务 13 | *

14 | * 15 | * @author 姚仲杰 16 | * @since 2022-07-05 17 | */ 18 | @Data 19 | @EqualsAndHashCode(callSuper = false) 20 | @Accessors(chain = true) 21 | public class ExcelTask implements Serializable { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | private Long id; 26 | 27 | /** 28 | * 类型:1-导入,2-导出 29 | */ 30 | private Integer type; 31 | 32 | /** 33 | * 状态:0-初始,1-进行中,2-完成,3-失败 34 | */ 35 | private Integer status; 36 | 37 | /** 38 | * 源文件 39 | */ 40 | private String sourceFile; 41 | 42 | /** 43 | * 预估记录数 可能包含空行数据不准确,但是大部分情况时准确的 44 | */ 45 | private Long estimateCount=0L; 46 | 47 | /** 48 | * 实际总记录数 为成功记录数+失败记录数 49 | */ 50 | private Long totalCount; 51 | 52 | /** 53 | * 成功记录数 54 | */ 55 | private Long successCount; 56 | 57 | /** 58 | * 失败记录数 59 | */ 60 | private Long failedCount; 61 | 62 | /** 63 | * 文件名 64 | */ 65 | private String fileName; 66 | 67 | /** 68 | * 成功文件路径 69 | */ 70 | private String fileUrl; 71 | 72 | /** 73 | * 失败文件路径 74 | */ 75 | private String failedFileUrl; 76 | 77 | /** 78 | * 失败消息 79 | */ 80 | private String failedMessage; 81 | 82 | /** 83 | * 导入开始时间 84 | */ 85 | private LocalDateTime startTime; 86 | 87 | /** 88 | * 导入结束时间 89 | */ 90 | private LocalDateTime endTime; 91 | 92 | /** 93 | * 租户编码,用于权限控制 94 | */ 95 | private String tenantCode; 96 | 97 | /** 98 | * 用户编码,用于权限控制 99 | */ 100 | private String createUserCode; 101 | 102 | /** 103 | * 业务编码 例如user,product,用于区分不同模块的导入 104 | */ 105 | private String businessCode; 106 | } 107 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/HeadContentUtil.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.alibaba.excel.annotation.ExcelProperty; 4 | import com.alibaba.excel.metadata.property.ExcelContentProperty; 5 | import com.alibaba.excel.util.MapUtils; 6 | import java.lang.reflect.Field; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * @Description TODO 15 | * @Author 姚仲杰#80998699 16 | * @Date 2022/7/11 15:55 17 | */ 18 | public class HeadContentUtil { 19 | 20 | public static final Map, Map> CLASS_HEAD_CONTENT_CACHE = new ConcurrentHashMap<>(); 21 | 22 | public static final List ignoreField=new ArrayList<>(3); 23 | static { 24 | ignoreField.add("row"); 25 | ignoreField.add("rowFailMessage"); 26 | ignoreField.add("sheetIndex"); 27 | } 28 | 29 | public static Map declaredFieldHeadContentMap(Class clazz) { 30 | if (clazz == null) { 31 | return null; 32 | } 33 | return CLASS_HEAD_CONTENT_CACHE.computeIfAbsent(clazz, key -> { 34 | List tempFieldList = new ArrayList<>(); 35 | Class tempClass = clazz; 36 | while (tempClass != null) { 37 | Collections.addAll(tempFieldList, tempClass.getDeclaredFields()); 38 | tempClass = tempClass.getSuperclass(); 39 | } 40 | 41 | Map headContentMap = MapUtils.newHashMapWithExpectedSize( 42 | tempFieldList.size()); 43 | int position=0; 44 | for (Field field : tempFieldList) { 45 | if (ignoreField.contains(field.getName())){ 46 | continue; 47 | } 48 | ExcelContentProperty excelContentProperty = new ExcelContentProperty(); 49 | excelContentProperty.setField(field); 50 | ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); 51 | String[] value = excelProperty.value(); 52 | headContentMap.put(position,value[0]); 53 | position++; 54 | } 55 | return headContentMap; 56 | }); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelHandleBasePackagesRegistrar.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.HashSet; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import org.springframework.beans.factory.config.BeanDefinitionHolder; 7 | import org.springframework.beans.factory.support.AbstractBeanDefinition; 8 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 9 | import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; 10 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 11 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 12 | import org.springframework.core.type.AnnotationMetadata; 13 | import org.springframework.util.ClassUtils; 14 | import org.springframework.util.StringUtils; 15 | 16 | /** 17 | * @Description 由于跨容器,所以通过EnableAsyncExcel注解将扫描的包基础路径传入子容器 18 | * @Author 姚仲杰#80998699 19 | * @Date 2022/7/7 23:09 20 | */ 21 | public class ExcelHandleBasePackagesRegistrar implements ImportBeanDefinitionRegistrar { 22 | 23 | @Override 24 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 25 | BeanDefinitionRegistry registry) { 26 | Set basePackages = getBasePackages(importingClassMetadata); 27 | BeanDefinitionBuilder definition = BeanDefinitionBuilder 28 | .genericBeanDefinition(ExcelHandleBasePackages.class); 29 | definition.addConstructorArgValue(basePackages); 30 | AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); 31 | String name = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, registry); 32 | BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition,name); 33 | BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); 34 | } 35 | 36 | protected Set getBasePackages(AnnotationMetadata importingClassMetadata) { 37 | Map attributes = importingClassMetadata 38 | .getAnnotationAttributes(EnableAsyncExcel.class.getCanonicalName()); 39 | 40 | Set basePackages = new HashSet<>(); 41 | for (String pkg : (String[]) attributes.get("basePackages")) { 42 | if (StringUtils.hasText(pkg)) { 43 | basePackages.add(pkg); 44 | } 45 | } 46 | 47 | if (basePackages.isEmpty()) { 48 | basePackages.add( 49 | ClassUtils.getPackageName(importingClassMetadata.getClassName())); 50 | } 51 | return basePackages; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/service/MockMultipartFile.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.service; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import org.springframework.lang.Nullable; 8 | import org.springframework.util.Assert; 9 | import org.springframework.util.FileCopyUtils; 10 | 11 | /** 12 | * @Description TODO 13 | * @Author 姚仲杰#80998699 14 | * @Date 2022/7/15 11:12 15 | */ 16 | public class MockMultipartFile { 17 | private final String name; 18 | private String originalFilename; 19 | @Nullable 20 | private String contentType; 21 | private final byte[] content; 22 | 23 | public MockMultipartFile(String name, @Nullable byte[] content) { 24 | this(name, "", (String)null, (byte[])content); 25 | } 26 | 27 | public MockMultipartFile(String name, InputStream contentStream) throws IOException { 28 | this(name, "", (String)null, (byte[])FileCopyUtils.copyToByteArray(contentStream)); 29 | } 30 | 31 | public MockMultipartFile(String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) { 32 | Assert.hasLength(name, "Name must not be null"); 33 | this.name = name; 34 | this.originalFilename = originalFilename != null ? originalFilename : ""; 35 | this.contentType = contentType; 36 | this.content = content != null ? content : new byte[0]; 37 | } 38 | 39 | public MockMultipartFile(String name, @Nullable String originalFilename, @Nullable String contentType, InputStream contentStream) throws IOException { 40 | this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream)); 41 | } 42 | 43 | public String getName() { 44 | return this.name; 45 | } 46 | 47 | public String getOriginalFilename() { 48 | return this.originalFilename; 49 | } 50 | 51 | @Nullable 52 | public String getContentType() { 53 | return this.contentType; 54 | } 55 | 56 | public boolean isEmpty() { 57 | return this.content.length == 0; 58 | } 59 | 60 | public long getSize() { 61 | return (long)this.content.length; 62 | } 63 | 64 | public byte[] getBytes() throws IOException { 65 | return this.content; 66 | } 67 | 68 | public InputStream getInputStream() throws IOException { 69 | return new ByteArrayInputStream(this.content); 70 | } 71 | 72 | public void transferTo(File dest) throws IOException, IllegalStateException { 73 | FileCopyUtils.copy(this.content, dest); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/context/mybatisplus/ExcelMybatisPlusConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot.context.mybatisplus; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; 5 | import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS; 6 | import com.baomidou.mybatisplus.core.config.GlobalConfig; 7 | import com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig; 8 | import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; 9 | import com.zaxxer.hikari.HikariDataSource; 10 | import javax.sql.DataSource; 11 | import org.apache.ibatis.mapping.DatabaseIdProvider; 12 | import org.apache.ibatis.session.SqlSessionFactory; 13 | import org.mybatis.spring.annotation.MapperScan; 14 | import org.springframework.beans.factory.annotation.Qualifier; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | /** 20 | * @Description TODO 21 | * @Author 姚仲杰#80998699 22 | * @Date 2022/7/5 16:37 23 | */ 24 | @Configuration 25 | @MapperScan(value = "com.asyncexcel.springboot.context.mapper",sqlSessionFactoryRef = "excelSqlSessionFactory") 26 | @EnableConfigurationProperties({ExcelDataSourceProperties.class,MybatisPlusProperties.class}) 27 | public class ExcelMybatisPlusConfiguration { 28 | 29 | @Bean("excelDataSource") 30 | public DataSource dataSource(ExcelDataSourceProperties properties){ 31 | HikariDataSource hikariDataSource = new HikariDataSource(); 32 | hikariDataSource.setJdbcUrl(properties.getUrl()); 33 | hikariDataSource.setUsername(properties.getUsername()); 34 | hikariDataSource.setPassword(properties.getPassword()); 35 | hikariDataSource.setDriverClassName(properties.getDriverClassName()); 36 | return hikariDataSource; 37 | } 38 | 39 | @Bean("excelSqlSessionFactory") 40 | public SqlSessionFactory sqlSessionFactory(@Qualifier("excelDataSource") DataSource dataSource) throws Exception { 41 | MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); 42 | mybatisSqlSessionFactoryBean.setVfs(SpringBootVFS.class); 43 | mybatisSqlSessionFactoryBean.setDataSource(dataSource); 44 | GlobalConfig globalConfig = new GlobalConfig(); 45 | DbConfig dbConfig = new DbConfig(); 46 | dbConfig.setIdType(IdType.AUTO); 47 | globalConfig.setDbConfig(dbConfig); 48 | mybatisSqlSessionFactoryBean.setGlobalConfig(globalConfig); 49 | return mybatisSqlSessionFactoryBean.getObject(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/AsyncExcelImporter.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.alibaba.excel.EasyExcel; 4 | import com.alibaba.excel.ExcelReader; 5 | import com.alibaba.excel.read.metadata.ReadSheet; 6 | import com.asyncexcel.core.ErrorMsg; 7 | import com.asyncexcel.core.ExceptionUtil; 8 | import java.util.List; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.function.Consumer; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * @Description TODO 16 | * @Author 姚仲杰#80998699 17 | * @Date 2022/7/11 10:14 18 | */ 19 | public class AsyncExcelImporter { 20 | 21 | private final static Logger log = LoggerFactory.getLogger(AsyncExcelImporter.class); 22 | ExecutorService executor; 23 | 24 | public AsyncExcelImporter(ExecutorService executor) { 25 | this.executor = executor; 26 | } 27 | 28 | public void importData(ImportHandler handler, ImportSupport support, 29 | DataImportParam param, 30 | ImportContext ctx) { 31 | Consumer> consumer = (dataList -> { 32 | support.onImport(ctx); 33 | try { 34 | handler.beforePerPage(ctx, dataList, param); 35 | List errorList = handler.importData(dataList, param); 36 | ctx.record(dataList.size(), errorList.size()); 37 | support.onWrite(dataList, ctx, errorList); 38 | handler.afterPerPage(ctx, dataList, param, errorList); 39 | } catch (Exception e) { 40 | log.error("导入过程异常"); 41 | if (e instanceof ImportException) { 42 | throw (ImportException) e; 43 | } else { 44 | throw ExceptionUtil.wrap2Runtime(e); 45 | } 46 | } 47 | }); 48 | AsyncPageReadListener asyncReadListener = new AsyncPageReadListener(consumer, support, ctx, 49 | param.getBatchSize()); 50 | ExcelReader reader = EasyExcel 51 | .read(param.getStream(), param.getModel(), asyncReadListener).build(); 52 | ReadSheet readSheet = EasyExcel.readSheet(0).build(); 53 | ctx.setReadSheet(readSheet); 54 | executor.execute(() -> { 55 | try { 56 | handler.init(ctx, param); 57 | support.beforeImport(); 58 | reader.read(ctx.getReadSheet()); 59 | support.onComplete(ctx); 60 | } catch (Exception e) { 61 | log.error("导入发生异常", e); 62 | if (e instanceof ImportException) { 63 | ctx.setFailMessage(e.getMessage()); 64 | } else { 65 | ctx.setFailMessage("系统异常,联系管理员"); 66 | } 67 | support.onError(ctx); 68 | } finally { 69 | handler.callBack(ctx, param); 70 | } 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | async-excel 7 | com.asyncexcel 8 | 1.1.2 9 | 10 | 4.0.0 11 | jar 12 | async-excel-springboot-starter 13 | async-excel-springboot-starter 14 | https://github.com/2229499815/async-excel.git 15 | AsyncExcel base on easy-excel for async 16 | 17 | 2.7.0 18 | 3.5.2 19 | 8.0.30 20 | 21 | 22 | 23 | com.asyncexcel 24 | async-excel-core 25 | ${project.version} 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-autoconfigure 30 | ${springboot.version} 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-jdbc 35 | ${springboot.version} 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-configuration-processor 40 | ${springboot.version} 41 | true 42 | 43 | 44 | com.baomidou 45 | mybatis-plus-boot-starter 46 | true 47 | ${baomidou.version} 48 | 49 | 50 | 51 | mysql 52 | mysql-connector-java 53 | ${mysql.version} 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-resources-plugin 63 | 2.4 64 | 65 | UTF-8 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-compiler-plugin 71 | 3.8.1 72 | 73 | 8 74 | 8 75 | UTF-8 76 | true 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelContextFactory.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import java.util.Collections; 4 | import java.util.Set; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.BeanFactoryUtils; 7 | import org.springframework.beans.factory.DisposableBean; 8 | import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ApplicationContextAware; 11 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 12 | import org.springframework.core.env.MapPropertySource; 13 | 14 | /** 15 | * @Description TODO 16 | * @Author 姚仲杰#80998699 17 | * @Date 2022/7/7 21:13 18 | */ 19 | public class ExcelContextFactory implements DisposableBean, ApplicationContextAware { 20 | 21 | private final String propertySourceName; 22 | 23 | public static Set basePackages; 24 | 25 | private final String propertyName; 26 | 27 | private final String contextName="excelContext"; 28 | 29 | private AnnotationConfigApplicationContext context; 30 | private ApplicationContext parent; 31 | 32 | private Class defaultConfigType; 33 | 34 | public ExcelContextFactory(Class defaultConfigType, String propertySourceName, 35 | String propertyName) { 36 | this.defaultConfigType = defaultConfigType; 37 | this.propertySourceName = propertySourceName; 38 | this.propertyName = propertyName; 39 | } 40 | 41 | @Override 42 | public void setApplicationContext(ApplicationContext parent) throws BeansException { 43 | this.parent = parent; 44 | } 45 | 46 | @Override 47 | public void destroy() { 48 | context.close(); 49 | } 50 | 51 | protected AnnotationConfigApplicationContext getContext() { 52 | if (this.context==null) { 53 | synchronized (AnnotationConfigApplicationContext.class) { 54 | if (this.context==null) { 55 | this.context=createContext(); 56 | } 57 | } 58 | } 59 | return this.context; 60 | } 61 | 62 | protected AnnotationConfigApplicationContext createContext() { 63 | AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); 64 | context.register(PropertyPlaceholderAutoConfiguration.class, 65 | this.defaultConfigType); 66 | 67 | context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( 68 | this.propertySourceName, 69 | Collections.singletonMap(this.propertyName, this.contextName))); 70 | if (this.parent != null) { 71 | ExcelHandleBasePackages ehbp = this.parent.getBean(ExcelHandleBasePackages.class); 72 | basePackages = ehbp.getBasePackages(); 73 | context.setParent(this.parent); 74 | context.setClassLoader(this.parent.getClassLoader()); 75 | } 76 | context.setDisplayName(generateDisplayName(this.contextName)); 77 | context.refresh(); 78 | return context; 79 | } 80 | 81 | protected String generateDisplayName(String name) { 82 | return this.getClass().getSimpleName() + "-" + name; 83 | } 84 | 85 | public T getInstance(Class type) { 86 | AnnotationConfigApplicationContext context = getContext(); 87 | if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, 88 | type).length > 0) { 89 | return context.getBean(type); 90 | } 91 | return null; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelContextRegistrar.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import com.asyncexcel.core.annotation.ExcelHandle; 4 | import java.util.Set; 5 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 6 | import org.springframework.beans.factory.config.BeanDefinition; 7 | import org.springframework.beans.factory.config.BeanDefinitionHolder; 8 | import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; 9 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 10 | import org.springframework.context.EnvironmentAware; 11 | import org.springframework.context.ResourceLoaderAware; 12 | import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; 13 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 14 | import org.springframework.core.env.Environment; 15 | import org.springframework.core.io.ResourceLoader; 16 | import org.springframework.core.type.AnnotationMetadata; 17 | import org.springframework.core.type.filter.AnnotationTypeFilter; 18 | 19 | /** 20 | * @Description TODO 21 | * @Author 姚仲杰#80998699 22 | * @Date 2022/7/7 17:05 23 | */ 24 | public class ExcelContextRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, 25 | EnvironmentAware { 26 | 27 | private ResourceLoader resourceLoader; 28 | 29 | private Environment environment; 30 | 31 | 32 | @Override 33 | public void setEnvironment(Environment environment) { 34 | this.environment = environment; 35 | } 36 | 37 | @Override 38 | public void setResourceLoader(ResourceLoader resourceLoader) { 39 | this.resourceLoader = resourceLoader; 40 | } 41 | 42 | @Override 43 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 44 | BeanDefinitionRegistry registry) { 45 | registerExcelHandle(importingClassMetadata,registry); 46 | } 47 | 48 | protected ClassPathScanningCandidateComponentProvider getScanner() { 49 | return new ClassPathScanningCandidateComponentProvider(false, this.environment) { 50 | @Override 51 | protected boolean isCandidateComponent( 52 | AnnotatedBeanDefinition beanDefinition) { 53 | boolean isCandidate = false; 54 | if (beanDefinition.getMetadata().isIndependent()) { 55 | if (!beanDefinition.getMetadata().isAnnotation()) { 56 | isCandidate = true; 57 | } 58 | } 59 | return isCandidate; 60 | } 61 | }; 62 | } 63 | 64 | 65 | public void registerExcelHandle(AnnotationMetadata metadata, 66 | BeanDefinitionRegistry registry) { 67 | ClassPathScanningCandidateComponentProvider scanner = getScanner(); 68 | scanner.setResourceLoader(this.resourceLoader); 69 | AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( 70 | ExcelHandle.class); 71 | 72 | scanner.addIncludeFilter(annotationTypeFilter); 73 | 74 | for (String basePackage : ExcelContextFactory.basePackages) { 75 | Set candidateComponents = scanner 76 | .findCandidateComponents(basePackage); 77 | for (BeanDefinition candidateComponent : candidateComponents) { 78 | if (candidateComponent instanceof AnnotatedBeanDefinition) { 79 | AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; 80 | String name = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, registry); 81 | BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, name); 82 | BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); 83 | } 84 | } 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/ImportContext.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.alibaba.excel.ExcelWriter; 4 | import com.alibaba.excel.read.metadata.ReadSheet; 5 | import com.alibaba.excel.write.metadata.WriteSheet; 6 | import com.asyncexcel.core.ExcelContext; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.util.concurrent.Future; 10 | 11 | /** 12 | * @Description TODO 13 | * @Author 姚仲杰#80998699 14 | * @Date 2022/7/11 10:13 15 | */ 16 | public class ImportContext extends ExcelContext { 17 | private String fileName; 18 | private String sheetName; 19 | private Class errorHeadClass; 20 | private ExcelWriter excelWriter; 21 | private WriteSheet writeSheet; 22 | private ReadSheet readSheet; 23 | 24 | private OutputStream outputStream; 25 | private InputStream inputStream; 26 | private Future future; 27 | private String errorFile; 28 | private String failMessage; 29 | private boolean validMaxRows; 30 | private int maxRows=1000; 31 | private boolean validHead=true; 32 | 33 | public boolean isValidHead() { 34 | return validHead; 35 | } 36 | 37 | public void setValidHead(boolean validHead) { 38 | this.validHead = validHead; 39 | } 40 | 41 | public boolean isValidMaxRows() { 42 | return validMaxRows; 43 | } 44 | 45 | public void setValidMaxRows(boolean validMaxRows) { 46 | this.validMaxRows = validMaxRows; 47 | } 48 | 49 | public int getMaxRows() { 50 | return maxRows; 51 | } 52 | 53 | public void setMaxRows(int maxRows) { 54 | this.maxRows = maxRows; 55 | } 56 | 57 | public String getFailMessage() { 58 | return failMessage; 59 | } 60 | 61 | public void setFailMessage(String failMessage) { 62 | this.failMessage = failMessage; 63 | } 64 | 65 | public Future getFuture() { 66 | return future; 67 | } 68 | 69 | public void setFuture(Future future) { 70 | this.future = future; 71 | } 72 | 73 | public String getFileName() { 74 | return fileName; 75 | } 76 | 77 | public void setFileName(String fileName) { 78 | this.fileName = fileName; 79 | } 80 | 81 | public String getErrorFile() { 82 | return errorFile; 83 | } 84 | 85 | public void setErrorFile(String errorFile) { 86 | this.errorFile = errorFile; 87 | } 88 | 89 | public ExcelWriter getExcelWriter() { 90 | return excelWriter; 91 | } 92 | 93 | public void setExcelWriter(ExcelWriter excelWriter) { 94 | this.excelWriter = excelWriter; 95 | } 96 | 97 | public WriteSheet getWriteSheet() { 98 | return writeSheet; 99 | } 100 | 101 | public void setWriteSheet(WriteSheet writeSheet) { 102 | this.writeSheet = writeSheet; 103 | } 104 | 105 | public ReadSheet getReadSheet() { 106 | return readSheet; 107 | } 108 | 109 | public void setReadSheet(ReadSheet readSheet) { 110 | this.readSheet = readSheet; 111 | } 112 | 113 | public OutputStream getOutputStream() { 114 | return outputStream; 115 | } 116 | 117 | public void setOutputStream(OutputStream outputStream) { 118 | this.outputStream = outputStream; 119 | } 120 | 121 | public InputStream getInputStream() { 122 | return inputStream; 123 | } 124 | 125 | public void setInputStream(InputStream inputStream) { 126 | this.inputStream = inputStream; 127 | } 128 | 129 | public Class getErrorHeadClass() { 130 | return errorHeadClass; 131 | } 132 | 133 | public void setErrorHeadClass(Class errorHeadClass) { 134 | this.errorHeadClass = errorHeadClass; 135 | } 136 | 137 | public String getSheetName() { 138 | return sheetName; 139 | } 140 | 141 | public void setSheetName(String sheetName) { 142 | this.sheetName = sheetName; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/ExportContext.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | import com.alibaba.excel.ExcelWriter; 4 | import com.alibaba.excel.converters.Converter; 5 | import com.alibaba.excel.write.handler.WriteHandler; 6 | import com.alibaba.excel.write.metadata.WriteSheet; 7 | import com.asyncexcel.core.ExcelContext; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.util.List; 11 | import java.util.concurrent.Future; 12 | 13 | /** 14 | * @Description TODO 15 | * @Author 姚仲杰#80998699 16 | * @Date 2022/7/11 10:12 17 | */ 18 | public class ExportContext extends ExcelContext { 19 | 20 | private OutputStream outputStream; 21 | private int limit = 1000; 22 | private ExcelWriter excelWriter; 23 | private WriteSheet writeSheet; 24 | private InputStream inputStream; 25 | private Class headClass; 26 | private Future future; 27 | private String resultFile; 28 | private String sheetName; 29 | private boolean dynamicHead; 30 | private List> headList; 31 | private String fileName; 32 | private List writeHandlers; 33 | private List> converters; 34 | private String failMessage; 35 | 36 | public int getLimit() { 37 | return limit; 38 | } 39 | 40 | public void setLimit(int limit) { 41 | this.limit = limit; 42 | } 43 | 44 | @Deprecated 45 | public List getWriteHandlers() { 46 | return writeHandlers; 47 | } 48 | 49 | @Deprecated 50 | public void setWriteHandlers(List writeHandlers) { 51 | this.writeHandlers = writeHandlers; 52 | } 53 | 54 | @Deprecated 55 | public List> getConverters() { 56 | return converters; 57 | } 58 | 59 | @Deprecated 60 | public void setConverters(List> converters) { 61 | this.converters = converters; 62 | } 63 | 64 | public boolean isDynamicHead() { 65 | return dynamicHead; 66 | } 67 | 68 | public void setDynamicHead(boolean dynamicHead) { 69 | this.dynamicHead = dynamicHead; 70 | } 71 | 72 | public List> getHeadList() { 73 | return headList; 74 | } 75 | 76 | public void setHeadList(List> headList) { 77 | this.headList = headList; 78 | } 79 | 80 | public String getResultFile() { 81 | return resultFile; 82 | } 83 | 84 | public void setResultFile(String resultFile) { 85 | this.resultFile = resultFile; 86 | } 87 | 88 | public String getFileName() { 89 | return fileName; 90 | } 91 | 92 | public void setFileName(String fileName) { 93 | this.fileName = fileName; 94 | } 95 | 96 | public String getSheetName() { 97 | return sheetName; 98 | } 99 | 100 | public void setSheetName(String sheetName) { 101 | this.sheetName = sheetName; 102 | } 103 | 104 | public Class getHeadClass() { 105 | return headClass; 106 | } 107 | 108 | public void setHeadClass(Class headClass) { 109 | this.headClass = headClass; 110 | } 111 | 112 | public OutputStream getOutputStream() { 113 | return outputStream; 114 | } 115 | 116 | public void setOutputStream(OutputStream outputStream) { 117 | this.outputStream = outputStream; 118 | } 119 | 120 | public ExcelWriter getExcelWriter() { 121 | return excelWriter; 122 | } 123 | 124 | public void setExcelWriter(ExcelWriter excelWriter) { 125 | this.excelWriter = excelWriter; 126 | } 127 | 128 | public WriteSheet getWriteSheet() { 129 | return writeSheet; 130 | } 131 | 132 | public void setWriteSheet(WriteSheet writeSheet) { 133 | this.writeSheet = writeSheet; 134 | } 135 | 136 | public InputStream getInputStream() { 137 | return inputStream; 138 | } 139 | 140 | public void setInputStream(InputStream inputStream) { 141 | this.inputStream = inputStream; 142 | } 143 | 144 | public Future getFuture() { 145 | return future; 146 | } 147 | 148 | public void setFuture(Future future) { 149 | this.future = future; 150 | } 151 | 152 | public String getFailMessage() { 153 | return failMessage; 154 | } 155 | 156 | public void setFailMessage(String failMessage) { 157 | this.failMessage = failMessage; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/AsyncExcelExporter.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | import com.asyncexcel.core.ExceptionUtil; 4 | import com.asyncexcel.core.ExportPage; 5 | import com.asyncexcel.core.TriFunction; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.function.BiFunction; 8 | import org.apache.commons.collections4.CollectionUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * @Description TODO 14 | * @Author 姚仲杰#80998699 15 | * @Date 2022/7/11 10:14 16 | */ 17 | public class AsyncExcelExporter { 18 | 19 | private final static Logger log = LoggerFactory.getLogger(AsyncExcelExporter.class); 20 | ExecutorService executor; 21 | 22 | public AsyncExcelExporter(ExecutorService executor) { 23 | this.executor = executor; 24 | } 25 | 26 | @Deprecated 27 | public void exportData(ExportHandler handler, ExportSupport support, DataExportParam param, 28 | ExportContext ctx) { 29 | 30 | BiFunction dataFunction = (start, limit) -> { 31 | support.onExport(ctx); 32 | try { 33 | handler.beforePerPage(ctx, param); 34 | ExportPage exportPage = handler.exportData(start, limit, param); 35 | if (exportPage == null) { 36 | throw new RuntimeException("导出数据为空"); 37 | } 38 | if (CollectionUtils.isEmpty(exportPage.getRecords())) { 39 | return exportPage; 40 | } 41 | ctx.record(exportPage.getRecords().size()); 42 | support.onWrite(exportPage.getRecords(), ctx); 43 | handler.afterPerPage(exportPage.getRecords(), ctx, param); 44 | return exportPage; 45 | } catch (Exception e) { 46 | log.error("导出过程发生异常"); 47 | if (e instanceof ExportException) { 48 | throw (ExportException) e; 49 | } else { 50 | throw ExceptionUtil.wrap2Runtime(e); 51 | } 52 | } 53 | }; 54 | executor.execute(() -> { 55 | try { 56 | handler.init(ctx, param); 57 | int cursor = 1; 58 | ExportPage page = dataFunction.apply(cursor, param.getLimit()); 59 | Long total = page.getTotal(); 60 | ctx.getTask().setEstimateCount(total); 61 | long pageNum = (total + page.getSize() - 1) / page.getSize(); 62 | for (cursor++; cursor <= pageNum; cursor++) { 63 | dataFunction.apply(cursor, param.getLimit()); 64 | } 65 | support.onComplete(ctx); 66 | } catch (Exception e) { 67 | log.error("导出异常", e); 68 | if (e instanceof ExportException) { 69 | ctx.setFailMessage(e.getMessage()); 70 | } else { 71 | ctx.setFailMessage("系统异常,联系管理员"); 72 | } 73 | support.onError(ctx); 74 | } finally { 75 | handler.callBack(ctx, param); 76 | } 77 | }); 78 | } 79 | 80 | /** 81 | * 支持多sheet导出,支持按批次进行单元格合并等功能 82 | * 83 | * @param handlers 84 | * @param support 85 | * @param param 86 | * @param ctx 87 | */ 88 | public void exportData(ExportSupport support, DataExportParam param, ExportContext ctx, 89 | ExportHandler... handlers) { 90 | TriFunction dataFunction = (h, start, limit) -> { 91 | support.onExport(ctx); 92 | try { 93 | h.beforePerPage(ctx, param); 94 | ExportPage exportPage = h.exportData(start, limit, param); 95 | if (CollectionUtils.isEmpty(exportPage.getRecords())) { 96 | return exportPage; 97 | } 98 | ctx.record(exportPage.getRecords().size()); 99 | support.onWrite(exportPage.getRecords(), ctx); 100 | h.afterPerPage(exportPage.getRecords(), ctx, param); 101 | return exportPage; 102 | } catch (Exception e) { 103 | log.error("导出过程发生异常"); 104 | if (e instanceof ExportException) { 105 | throw (ExportException) e; 106 | } else { 107 | throw ExceptionUtil.wrap2Runtime(e); 108 | } 109 | } 110 | }; 111 | 112 | executor.execute(() -> { 113 | try { 114 | if (handlers == null || handlers.length == 0) { 115 | throw new ExportException("未设置导出处理类"); 116 | } 117 | int sheetNo = 0; 118 | for (ExportHandler handler : handlers) { 119 | handler.init(ctx, param); 120 | if (ctx.getWriteSheet() != null) { 121 | ctx.getWriteSheet().setSheetNo(sheetNo); 122 | } 123 | sheetNo++; 124 | int cursor = 1; 125 | ExportPage page = dataFunction.apply(handler, cursor, ctx.getLimit()); 126 | Long total = page.getTotal(); 127 | ctx.getTask().setEstimateCount(total + ctx.getTask().getEstimateCount()); 128 | Long pageNum = (total + page.getSize() - 1) / page.getSize(); 129 | for (cursor++; cursor <= pageNum; cursor++) { 130 | dataFunction.apply(handler, cursor, ctx.getLimit()); 131 | } 132 | } 133 | support.onComplete(ctx); 134 | } catch (Exception e) { 135 | log.error("导出异常", e); 136 | if (e instanceof ExportException) { 137 | ctx.setFailMessage(e.getMessage()); 138 | } else { 139 | ctx.setFailMessage("系统异常,联系管理员"); 140 | } 141 | support.onError(ctx); 142 | } finally { 143 | for (ExportHandler handler : handlers) { 144 | handler.callBack(ctx, param); 145 | } 146 | } 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/exporter/AsyncExportTaskSupport.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.exporter; 2 | 3 | import com.alibaba.excel.EasyExcel; 4 | import com.alibaba.excel.ExcelWriter; 5 | import com.alibaba.excel.converters.Converter; 6 | import com.alibaba.excel.support.ExcelTypeEnum; 7 | import com.alibaba.excel.write.builder.ExcelWriterBuilder; 8 | import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; 9 | import com.alibaba.excel.write.handler.WriteHandler; 10 | import com.alibaba.excel.write.metadata.WriteSheet; 11 | import com.asyncexcel.core.ExceptionUtil; 12 | import com.asyncexcel.core.model.ExcelTask; 13 | import com.asyncexcel.core.service.IStorageService; 14 | import com.asyncexcel.core.service.TaskService; 15 | import java.io.IOException; 16 | import java.io.PipedInputStream; 17 | import java.io.PipedOutputStream; 18 | import java.time.LocalDateTime; 19 | import java.util.Collection; 20 | import java.util.concurrent.FutureTask; 21 | import org.apache.commons.collections4.CollectionUtils; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | /** 26 | * @Description TODO 27 | * @Author 姚仲杰#80998699 28 | * @Date 2022/7/15 14:39 29 | */ 30 | public class AsyncExportTaskSupport implements ExportSupport { 31 | 32 | private final static Logger log = LoggerFactory.getLogger(AsyncExportTaskSupport.class); 33 | IStorageService storageService; 34 | TaskService taskService; 35 | 36 | public AsyncExportTaskSupport(IStorageService storageService, 37 | TaskService taskService) { 38 | this.storageService = storageService; 39 | this.taskService = taskService; 40 | } 41 | 42 | @Override 43 | public ExcelTask createTask(DataExportParam param) { 44 | ExcelTask task = new ExcelTask(); 45 | task.setType(2); 46 | task.setStatus(0); 47 | task.setStartTime(LocalDateTime.now()); 48 | task.setTenantCode(param.getTenantCode()); 49 | task.setCreateUserCode(param.getCreateUserCode()); 50 | task.setBusinessCode(param.getBusinessCode()); 51 | task.setFileName(param.getExportFileName()); 52 | taskService.save(task); 53 | return task; 54 | } 55 | 56 | @Override 57 | public void onExport(ExportContext ctx) { 58 | ExcelTask excelTask = ctx.getTask(); 59 | excelTask.setStatus(1); 60 | excelTask.setFailedCount(ctx.getFailCount()); 61 | excelTask.setSuccessCount(ctx.getSuccessCount()); 62 | excelTask.setTotalCount(ctx.getTotalCount()); 63 | taskService.updateById(excelTask); 64 | } 65 | 66 | @Override 67 | public void onWrite(Collection dataList, ExportContext ctx) { 68 | if (CollectionUtils.isEmpty(dataList)) { 69 | return; 70 | } 71 | if (ctx.getOutputStream() == null) { 72 | PipedOutputStream pos = new PipedOutputStream(); 73 | try { 74 | PipedInputStream pis = new PipedInputStream(pos); 75 | ctx.setInputStream(pis); 76 | //此处单独起线程避免线程互相等待死锁 77 | FutureTask futureTask = new FutureTask<>( 78 | () -> storageService.write(ctx.getFileName(), ctx.getInputStream())); 79 | new Thread(futureTask).start(); 80 | ctx.setFuture(futureTask); 81 | } catch (IOException e) { 82 | ExceptionUtil.wrap2Runtime(e); 83 | } 84 | ctx.setOutputStream(pos); 85 | } 86 | 87 | //创建excel 88 | if (ctx.getExcelWriter() == null) { 89 | ExcelWriterBuilder writerBuilder = EasyExcel.write(ctx.getOutputStream()) 90 | .excelType(ExcelTypeEnum.XLSX).autoCloseStream(false); 91 | ExcelWriter excelWriter = writerBuilder.build(); 92 | 93 | ctx.setExcelWriter(excelWriter); 94 | } 95 | //创建sheet 此代码块将在后续某个版本的迭代中移除,sheet将由handler的init方法创建 96 | if (ctx.getWriteSheet()==null){ 97 | ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet(0) 98 | .sheetName(ctx.getSheetName()); 99 | //动态表头以sheet为单位 100 | if (ctx.isDynamicHead()) { 101 | sheetBuilder.head(ctx.getHeadList()); 102 | } else { 103 | sheetBuilder.head(ctx.getHeadClass()); 104 | } 105 | //自定义样式以sheet为单位 106 | if (ctx.getWriteHandlers() != null && ctx.getWriteHandlers().size() > 0) { 107 | for (WriteHandler writeHandler : ctx.getWriteHandlers()) { 108 | sheetBuilder.registerWriteHandler(writeHandler); 109 | } 110 | } 111 | //自定义类型转换器以sheet为单位 112 | if (ctx.getConverters() != null && ctx.getConverters().size() > 0) { 113 | for (Converter converter : ctx.getConverters()) { 114 | sheetBuilder.registerConverter(converter); 115 | } 116 | } 117 | WriteSheet writeSheet = sheetBuilder.build(); 118 | ctx.setWriteSheet(writeSheet); 119 | } 120 | 121 | ctx.getExcelWriter().write(dataList, ctx.getWriteSheet()); 122 | 123 | } 124 | 125 | public void close(ExportContext ctx) { 126 | if (ctx.getExcelWriter() != null) { 127 | ctx.getExcelWriter().finish(); 128 | } 129 | if (ctx.getOutputStream() != null) { 130 | try { 131 | ctx.getOutputStream().close(); 132 | } catch (Exception e) { 133 | e.printStackTrace(); 134 | } 135 | } 136 | if (ctx.getInputStream() != null) { 137 | try { 138 | ctx.setResultFile(ctx.getFuture().get()); 139 | ctx.getInputStream().close(); 140 | } catch (Exception e) { 141 | e.printStackTrace(); 142 | } 143 | } 144 | } 145 | 146 | @Override 147 | public void onComplete(ExportContext ctx) { 148 | close(ctx); 149 | ExcelTask excelTask = ctx.getTask(); 150 | excelTask.setStatus(2); 151 | excelTask.setFailedCount(ctx.getFailCount()); 152 | excelTask.setSuccessCount(ctx.getSuccessCount()); 153 | excelTask.setEndTime(LocalDateTime.now()); 154 | excelTask.setTotalCount(ctx.getTotalCount()); 155 | excelTask.setFileUrl(ctx.getResultFile()); 156 | taskService.updateById(excelTask); 157 | if (log.isDebugEnabled()) { 158 | log.debug("task completed"); 159 | } 160 | } 161 | 162 | @Override 163 | public void onError(ExportContext ctx) { 164 | close(ctx); 165 | ExcelTask excelTask = ctx.getTask(); 166 | excelTask.setStatus(3); 167 | excelTask.setFailedCount(ctx.getFailCount()); 168 | excelTask.setSuccessCount(ctx.getSuccessCount()); 169 | excelTask.setEndTime(LocalDateTime.now()); 170 | excelTask.setTotalCount(ctx.getTotalCount()); 171 | excelTask.setFileUrl(ctx.getResultFile()); 172 | excelTask.setFailedMessage(ctx.getFailMessage()); 173 | taskService.updateById(excelTask); 174 | if (log.isDebugEnabled()) { 175 | log.debug("task Error"); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/AsyncPageReadListener.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.alibaba.excel.context.AnalysisContext; 4 | import com.alibaba.excel.exception.ExcelDataConvertException; 5 | import com.alibaba.excel.metadata.CellExtra; 6 | import com.alibaba.excel.metadata.data.ReadCellData; 7 | import com.alibaba.excel.read.listener.ReadListener; 8 | import com.alibaba.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder; 9 | import com.alibaba.excel.util.ConverterUtils; 10 | import com.alibaba.excel.util.ListUtils; 11 | import com.alibaba.excel.util.StringUtils; 12 | import com.asyncexcel.core.ExceptionUtil; 13 | import com.asyncexcel.core.ISheetRow; 14 | import com.asyncexcel.core.ImportRowMap; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.function.Consumer; 19 | import java.util.stream.Collectors; 20 | import org.apache.commons.collections4.CollectionUtils; 21 | 22 | /** 23 | * @Description TODO 24 | * @Author 姚仲杰#80998699 25 | * @Date 2022/7/12 17:38 26 | */ 27 | public class AsyncPageReadListener implements ReadListener { 28 | 29 | private int batchSize = 100; 30 | private List cachedDataList = ListUtils.newArrayListWithExpectedSize(batchSize); 31 | private final Consumer> consumer; 32 | private ImportContext ctx; 33 | private Map headMap; 34 | private ImportSupport support; 35 | 36 | public AsyncPageReadListener(Consumer> consumer,ImportSupport support, ImportContext ctx, 37 | int batchSize) { 38 | if (batchSize > 0) { 39 | this.batchSize = batchSize; 40 | } 41 | this.ctx = ctx; 42 | this.consumer = consumer; 43 | this.support = support; 44 | } 45 | 46 | @Override 47 | public void invokeHead(Map> readHead, AnalysisContext context) { 48 | // todo 表头校验,这里可以从数据库获取 49 | this.headMap = ConverterUtils.convertToStringMap(readHead, context); 50 | if (ctx.isValidHead()){ 51 | if (headMap.size()==0){ 52 | throw new HeadCheckException("表头不能为空"); 53 | } 54 | Map headContentMap = HeadContentUtil 55 | .declaredFieldHeadContentMap(context.readSheetHolder().getClazz()); 56 | if (headContentMap.size()==0) { 57 | throw new HeadCheckException("表头错误联系管理员"); 58 | } 59 | 60 | StringBuilder sb=new StringBuilder(); 61 | headMap.forEach((k,v)->{ 62 | sb.append(v).append(","); 63 | }); 64 | sb.replace(sb.lastIndexOf(","),sb.length(),""); 65 | String readHeadString = sb.toString(); 66 | sb.replace(0,sb.length(),""); 67 | headContentMap.forEach((k,v)->{ 68 | sb.append(v).append(","); 69 | }); 70 | sb.replace(sb.lastIndexOf(","),sb.length(),""); 71 | String confHeadString=sb.toString(); 72 | if (readHeadString!=null&&!readHeadString.equals(confHeadString)){ 73 | throw new HeadCheckException("表头校验失败,表头格式为:{"+confHeadString+"}"); 74 | } 75 | } 76 | } 77 | 78 | @Override 79 | public void extra(CellExtra extra, AnalysisContext context) { 80 | // todo 一些前置条件配置校验 81 | } 82 | 83 | @Override 84 | public void onException(Exception exception, AnalysisContext context) throws Exception { 85 | // todo 处理异常 数据转换异常处理,是否忽略等 86 | if (exception instanceof MaxRowsLimitException){ 87 | throw new MaxRowsLimitException(exception.getMessage()); 88 | }else if (exception instanceof ExcelDataConvertException){ 89 | int col = 0,row =0; 90 | String title = ""; 91 | ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; 92 | col = excelDataConvertException.getColumnIndex(); 93 | row = excelDataConvertException.getRowIndex(); 94 | title = this.headMap.get(col); 95 | Map> cellMap = (Map>)context.readRowHolder().getCurrentRowAnalysisResult(); 96 | Map cellStringMap = ConverterUtils 97 | .convertToStringMap(cellMap, context); 98 | //数据对齐表头 99 | if (cellStringMap.size() rowList = cellStringMap.values().stream().collect(Collectors.toList()); 107 | List> lists = new ArrayList<>(); 108 | lists.add(rowList); 109 | ctx.record(1,1); 110 | this.support.onWrite(lists,ctx,null); 111 | }else if(exception instanceof HeadCheckException){ 112 | throw new HeadCheckException(exception.getMessage()); 113 | }else { 114 | throw ExceptionUtil.wrap2Runtime(exception); 115 | } 116 | } 117 | 118 | @Override 119 | public void invoke(T data, AnalysisContext context) { 120 | if (StringUtils.isBlank(ctx.getSheetName())) { 121 | String sheetName = ((XlsxReadSheetHolder) context 122 | .currentReadHolder()).getSheetName(); 123 | ctx.setSheetName(sheetName); 124 | } 125 | Integer rowIndex = context.readRowHolder().getRowIndex(); 126 | //20221025 添加动态表头支持 127 | if (data instanceof ImportRowMap){ 128 | ImportRowMap rowMap = (ImportRowMap) data; 129 | rowMap.setDataMap(context.readRowHolder().getCellMap()); 130 | rowMap.setHeadMap(headMap); 131 | } 132 | 133 | if (data instanceof ISheetRow) { 134 | ISheetRow rowData = (ISheetRow) data; 135 | rowData.setRow(rowIndex); 136 | } else { 137 | throw new RuntimeException("导入对应实体必须继承ISheetRow"); 138 | } 139 | if (ctx.getTask().getEstimateCount() == 0L) { 140 | Integer headRowNumber = context.readSheetHolder().getHeadRowNumber(); 141 | Integer totalCount = context.getTotalCount() - headRowNumber; 142 | ctx.getTask().setEstimateCount(totalCount.longValue()); 143 | } 144 | if (ctx.isValidMaxRows()){ 145 | if (ctx.getTask().getEstimateCount()>ctx.getMaxRows()){ 146 | throw new MaxRowsLimitException("行数限制{"+ctx.getMaxRows()+"}行,包含表头与空行"); 147 | } 148 | } 149 | cachedDataList.add(data); 150 | if (cachedDataList.size() >= this.batchSize) { 151 | consumer.accept(cachedDataList); 152 | cachedDataList = ListUtils.newArrayListWithExpectedSize(this.batchSize); 153 | } 154 | } 155 | 156 | @Override 157 | public void doAfterAllAnalysed(AnalysisContext context) { 158 | if (CollectionUtils.isNotEmpty(cachedDataList)) { 159 | consumer.accept(cachedDataList); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /async-excel-springboot-starter/src/main/java/com/asyncexcel/springboot/ExcelService.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.springboot; 2 | 3 | import com.alibaba.excel.support.ExcelTypeEnum; 4 | import com.asyncexcel.core.exporter.AsyncExcelExporter; 5 | import com.asyncexcel.core.exporter.DataExportParam; 6 | import com.asyncexcel.core.exporter.ExportContext; 7 | import com.asyncexcel.core.exporter.ExportHandler; 8 | import com.asyncexcel.core.exporter.ExportSupport; 9 | import com.asyncexcel.core.importer.AsyncExcelImporter; 10 | import com.asyncexcel.core.importer.DataImportParam; 11 | import com.asyncexcel.core.importer.ImportContext; 12 | import com.asyncexcel.core.importer.ImportHandler; 13 | import com.asyncexcel.core.importer.ImportSupport; 14 | import com.asyncexcel.core.model.ExcelTask; 15 | import com.asyncexcel.springboot.context.service.IExcelTaskService; 16 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 17 | import com.baomidou.mybatisplus.core.metadata.IPage; 18 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.springframework.util.StringUtils; 22 | 23 | /** 24 | * @Description TODO 25 | * @Author 姚仲杰#80998699 26 | * @Date 2022/7/8 0:07 27 | */ 28 | public class ExcelService { 29 | private final static Logger log= LoggerFactory.getLogger(ExcelService.class); 30 | 31 | ExcelThreadPool excelThreadPool; 32 | 33 | SpringExcelContext context; 34 | 35 | public ExcelService(ExcelThreadPool excelThreadPool, SpringExcelContext context) { 36 | this.excelThreadPool = excelThreadPool; 37 | this.context = context; 38 | } 39 | 40 | public Long doImport(Class cls, DataImportParam param){ 41 | ImportHandler handler = context.getInstance(cls); 42 | ImportSupport support = context.getInstance(ImportSupport.class); 43 | ExcelTask task = support.createTask(param); 44 | log.info("添加任务taskId:{}",task.getId()); 45 | ImportContext ctx=new ImportContext(); 46 | ctx.setTask(task); 47 | ctx.setFileName(param.getFilename()); 48 | ctx.setErrorHeadClass(param.getModel()); 49 | ctx.setValidMaxRows(param.isValidMaxRows()); 50 | ctx.setMaxRows(param.getMaxRows()); 51 | ctx.setValidHead(param.isValidHead()); 52 | AsyncExcelImporter asyncExcelImporter = new AsyncExcelImporter(excelThreadPool.getExecutor()); 53 | asyncExcelImporter.importData(handler,support,param,ctx); 54 | return task.getId(); 55 | } 56 | 57 | public Long doExport(Class cls, DataExportParam param){ 58 | String filePrefix="导出"; 59 | ExportHandler handler = context.getInstance(cls); 60 | ExportSupport support=context.getInstance(ExportSupport.class); 61 | ExcelTask task = support.createTask(param); 62 | ExportContext ctx=new ExportContext(); 63 | ctx.setTask(task); 64 | ctx.setLimit(param.getLimit()); 65 | ctx.setHeadClass(param.getHeadClass()); 66 | ctx.setDynamicHead(param.isDynamicHead()); 67 | ctx.setHeadList(param.getHeadList()); 68 | ctx.setWriteHandlers(param.getWriteHandlers()); 69 | ctx.setConverters(param.getConverters()); 70 | ctx.setSheetName(param.getSheetName()); 71 | String fileName=param.getExportFileName(); 72 | StringBuilder sb=new StringBuilder(filePrefix).append(task.getId()).append("-"); 73 | if (StringUtils.isEmpty(fileName)){ 74 | sb.append(ExcelTypeEnum.XLSX.getValue()); 75 | }else { 76 | if (fileName.lastIndexOf(".")!=-1){ 77 | String extension = fileName.substring(fileName.lastIndexOf(".")); 78 | if (!ExcelTypeEnum.XLSX.getValue().equals(extension)) { 79 | sb.append(fileName).append(ExcelTypeEnum.XLSX.getValue()); 80 | }else{ 81 | sb.append(fileName); 82 | } 83 | }else{ 84 | sb.append(fileName).append(ExcelTypeEnum.XLSX.getValue()); 85 | } 86 | } 87 | ctx.setFileName(sb.toString()); 88 | AsyncExcelExporter asyncExcelExporter=new AsyncExcelExporter(excelThreadPool.getExecutor()); 89 | asyncExcelExporter.exportData(handler,support,param,ctx); 90 | return task.getId(); 91 | } 92 | 93 | public Long doExport(DataExportParam param, Class... clses) { 94 | String filePrefix = "导出"; 95 | ExportHandler[] handlers = new ExportHandler[clses.length]; 96 | for (int i = 0; i < clses.length; i++) { 97 | ExportHandler handler = context.getInstance(clses[i]); 98 | handlers[i]=handler; 99 | } 100 | 101 | ExportSupport support = context.getInstance(ExportSupport.class); 102 | ExcelTask task = support.createTask(param); 103 | ExportContext ctx = new ExportContext(); 104 | ctx.setLimit(param.getLimit()); 105 | ctx.setTask(task); 106 | ctx.setHeadClass(param.getHeadClass()); 107 | ctx.setDynamicHead(param.isDynamicHead()); 108 | ctx.setHeadList(param.getHeadList()); 109 | ctx.setWriteHandlers(param.getWriteHandlers()); 110 | ctx.setConverters(param.getConverters()); 111 | ctx.setSheetName(param.getSheetName()); 112 | String fileName = param.getExportFileName(); 113 | StringBuilder sb = new StringBuilder(filePrefix).append(task.getId()).append("-"); 114 | if (StringUtils.isEmpty(fileName)) { 115 | sb.append(ExcelTypeEnum.XLSX.getValue()); 116 | } else { 117 | if (fileName.lastIndexOf(".") != -1) { 118 | String extension = fileName.substring(fileName.lastIndexOf(".")); 119 | if (!ExcelTypeEnum.XLSX.getValue().equals(extension)) { 120 | sb.append(fileName).append(ExcelTypeEnum.XLSX.getValue()); 121 | } else { 122 | sb.append(fileName); 123 | } 124 | } else { 125 | sb.append(fileName).append(ExcelTypeEnum.XLSX.getValue()); 126 | } 127 | } 128 | ctx.setFileName(sb.toString()); 129 | AsyncExcelExporter asyncExcelExporter = new AsyncExcelExporter( 130 | excelThreadPool.getExecutor()); 131 | asyncExcelExporter.exportData(support, param, ctx, handlers); 132 | return task.getId(); 133 | } 134 | /**提供给外部定制划按权限分页查询 135 | * @param task 136 | * @return 137 | */ 138 | public IPage listPage(ExcelTask task,int currentPage,int limit){ 139 | IExcelTaskService excelTaskService = context.getInstance(IExcelTaskService.class); 140 | LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper(); 141 | queryWrapper.orderByDesc(ExcelTask::getId); 142 | queryWrapper.eq(!StringUtils.isEmpty(task.getId()),ExcelTask::getId,task.getId()); 143 | queryWrapper.eq(!StringUtils.isEmpty(task.getBusinessCode()),ExcelTask::getBusinessCode,task.getBusinessCode()); 144 | queryWrapper.eq(!StringUtils.isEmpty(task.getCreateUserCode()),ExcelTask::getCreateUserCode,task.getCreateUserCode()); 145 | queryWrapper.eq(!StringUtils.isEmpty(task.getTenantCode()),ExcelTask::getTenantCode,task.getTenantCode()); 146 | IPage pageParam=new Page<>(currentPage,limit); 147 | IPage page = excelTaskService.page(pageParam,queryWrapper); 148 | return page; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.asyncexcel 8 | async-excel 9 | pom 10 | 1.1.2 11 | async-excel 12 | https://github.com/2229499815/async-excel.git 13 | AsyncExcel base on easy-excel for async 14 | 15 | async-excel-springboot-starter 16 | async-excel-core 17 | 18 | 19 | 1.8 20 | 1.18.4 21 | 3.1.1 22 | 1.21 23 | 1.15 24 | 25 | 26 | 27 | 28 | org.apache.commons 29 | commons-compress 30 | ${commons-compress.version} 31 | 32 | 33 | commons-codec 34 | commons-codec 35 | ${commons-codec.version} 36 | 37 | 38 | com.alibaba 39 | easyexcel 40 | ${easyexcel.version} 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | ${lombok.version} 50 | 51 | 52 | com.alibaba 53 | easyexcel 54 | 55 | 56 | 57 | 58 | 59 | 60 | The Apache Software License, Version 2.0 61 | http://www.apache.org/licenses/LICENSE-2.0.txt 62 | 63 | 64 | 65 | 66 | master 67 | https://github.com/2229499815/async-excel.git 68 | scm:git:https://github.com/2229499815/async-excel.git 69 | scm:git:git@github.com:2229499815/async-excel.git 70 | 71 | 72 | 73 | 74 | yaozhongjie 75 | 2229499815@qq.com 76 | https://github.com/2229499815 77 | 78 | 79 | 80 | 81 | 82 | release 83 | 84 | true 85 | 86 | 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-source-plugin 92 | 3.2.1 93 | 94 | 95 | attach-sources 96 | 97 | jar-no-fork 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-javadoc-plugin 105 | 3.4.1 106 | 107 | 108 | package 109 | 110 | jar 111 | 112 | 113 | none 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-gpg-plugin 122 | 3.0.1 123 | 124 | 125 | sign-artifacts 126 | verify 127 | 128 | sign 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-source-plugin 140 | 3.2.1 141 | 142 | 143 | attach-sources 144 | 145 | jar-no-fork 146 | 147 | 148 | 149 | 150 | 151 | org.apache.maven.plugins 152 | maven-javadoc-plugin 153 | 3.4.1 154 | 155 | UTF-8 156 | UTF-8 157 | UTF-8 158 | 159 | 160 | 161 | package 162 | 163 | jar 164 | 165 | 166 | none 167 | 168 | 169 | 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-gpg-plugin 175 | 3.0.1 176 | 177 | 178 | sign-artifacts 179 | verify 180 | 181 | sign 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | oss 191 | https://s01.oss.sonatype.org/content/repositories/snapshots 192 | 193 | 194 | oss 195 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # async-excel 3 | 一个基于easyexcel大数据量数据导入导出异步处理组件,如果你觉得对你有帮助,请点击右上角的star,支持下 4 | 5 | ## asyncexcel介绍 6 | 7 | + 1、asyncexcel基于阿里的easyexcel包装,抽取异步骨架,不改变easyexcel的特性 8 | 9 | 支持的功能列表: 10 | + 支持线程池外部声明,可传入SystemContext; 11 | + 仅支持单行表头 12 | + 支持表头校验 13 | + 支持格式转换错误校验出错写入错误文件 14 | + 支持业务错误写出错误文件 15 | + 支持行数限制 16 | + 支持不分页事务 17 | + 支持查看进度 18 | + 支持异步分批次导入导出,分页大小可自定义 19 | + 支持动态表头导出 20 | + 支持多租户隔离 21 | + 支持多模块隔离 22 | + 支持用户权限隔离 23 | + 支持自定义存储,如果不设置默认使用本地存储,存储路径/tmp/upload 如果自定义只要实现接口IStorageService 实现String write(String name, InputStream data)方法即可 声明成bean即可 24 | 25 | ### 快速开始 26 | 引入starter 27 | ```xml 28 | 29 | com.asyncexcel 30 | async-excel-springboot-starter 31 | 1.0.0 32 | 33 | 34 | 35 | com.baomidou 36 | mybatis-plus-boot-starter 37 | 3.5.2 38 | 39 | 40 | 41 | mysql 42 | mysql-connector-java 43 | 8.0.29 44 | 45 | ``` 46 | 导入数据库 47 | ```sql 48 | drop table if exists excel_task; 49 | CREATE TABLE `excel_task` ( 50 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', 51 | `type` tinyint(2) NOT NULL COMMENT '类型:1-导入,2-导出', 52 | `status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '状态:0-初始,1-进行中,2-完成,3-失败', 53 | `estimate_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '预估总记录数', 54 | `total_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '实际总记录数', 55 | `success_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '成功记录数', 56 | `failed_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '失败记录数', 57 | `file_name` varchar(200) DEFAULT NULL COMMENT '文件名', 58 | `file_url` varchar(500) DEFAULT NULL COMMENT '文件路径', 59 | `failed_file_url` varchar(500) DEFAULT NULL COMMENT '失败文件路径', 60 | `failed_message` varchar(255) DEFAULT NULL COMMENT '失败消息', 61 | `start_time` datetime DEFAULT NULL COMMENT '开始时间', 62 | `end_time` datetime DEFAULT NULL COMMENT '结束时间', 63 | `tenant_code` varchar(50) default NULL COMMENT '租户编码', 64 | `create_user_code` varchar(50) default NULL COMMENT '用户编码', 65 | `business_code` varchar(50) default NULL COMMENT '业务编码', 66 | PRIMARY KEY (`id`) 67 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='导入导出任务'; 68 | ``` 69 | 配置数据源(此处为多数据源,使用了spring 父子容器技术,所以不影响你原本的数据源) 70 | ```properties 71 | #asyncexcel 数据源 72 | spring.excel.datasource.url=jdbc:mysql://localhost:3306/async-excel?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&&useCursorFetch=true&&rewriteBatchedStatements=true 73 | spring.excel.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 74 | spring.excel.datasource.password=root 75 | spring.excel.datasource.username=root 76 | #业务数据源 77 | spring.datasource.url=jdbc:mysql://localhost:3306/async-excel-sample?serverTimezone=GMT%2B8&autoReconnect=true&allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&&useCursorFetch=true&&rewriteBatchedStatements=true 78 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 79 | spring.datasource.password=root 80 | spring.datasource.username=root 81 | ``` 82 | 使用@EnableAsyncExcel注解启用配置 83 | ```java 84 | @SpringBootApplication 85 | @EnableAsyncExcel 86 | @MapperScan({"com.asyncexcel.sample.mapper"}) 87 | public class AsyncExcelSampleApplication { 88 | 89 | public static void main(String[] args) { 90 | SpringApplication.run(AsyncExcelSampleApplication.class, args); 91 | } 92 | 93 | } 94 | ``` 95 | 编写极简示例 [示例项目 async-excel-sample](https://github.com/2229499815/async-excel-sample) 欢迎添加使用示例 96 | ```java 97 | @RestController 98 | @RequestMapping("/user") 99 | public class UserController { 100 | 101 | @Resource 102 | ExcelService excelService; 103 | 104 | //导入最简示例 105 | @PostMapping("/imports") 106 | public Long imports(@RequestBody MultipartFile file) throws Exception{ 107 | DataImportParam dataImportParam = new DataImportParam() 108 | .setStream(file.getInputStream()) 109 | .setModel(UserImportModel.class) 110 | .setBatchSize(3) 111 | .setFilename("用户导入"); 112 | Long taskId = excelService.doImport(UserImportHandler.class, dataImportParam); 113 | return taskId; 114 | } 115 | 116 | //导出最简示例 117 | @PostMapping("/exports") 118 | public Long exports(){ 119 | DataExportParam dataExportParam=new DataExportParam() 120 | .setExportFileName("用户导出") 121 | .setLimit(5) 122 | .setHeadClass(UserExportModel.class); 123 | return excelService.doExport(UserExportHandler.class,dataExportParam); 124 | } 125 | 126 | } 127 | ``` 128 | 导入导出model 129 | ```java 130 | @Data 131 | public class UserExportModel extends ExportRow { 132 | 133 | @ExcelProperty("用户编码") 134 | private String userCode; 135 | 136 | @ExcelProperty("用户姓名") 137 | private String userName; 138 | 139 | @ExcelProperty("手机号") 140 | private String mobile; 141 | 142 | @ExcelProperty("备注") 143 | private String remarks; 144 | 145 | } 146 | ``` 147 | ```java 148 | @Data 149 | public class UserImportModel extends ImportRow { 150 | 151 | @ExcelProperty("用户编码") 152 | private String userCode; 153 | 154 | @ExcelProperty("用户姓名") 155 | private String userName; 156 | 157 | @ExcelProperty("手机号") 158 | private String mobile; 159 | 160 | @ExcelProperty("备注") 161 | private String remarks; 162 | } 163 | 164 | ``` 165 | 编写导入导出处理类 166 | ```java 167 | @ExcelHandle 168 | public class UserExportHandler implements ExportHandler { 169 | 170 | @Autowired 171 | IUserService userService; 172 | @Override 173 | public ExportPage exportData(int startPage, int limit, DataExportParam dataExportParam) { 174 | IPage iPage = new Page<>(startPage, limit); 175 | IPage page = userService.page(iPage); 176 | List list = ExportListUtil.transform(page.getRecords(), UserExportModel.class); 177 | ExportPage result = new ExportPage<>(); 178 | result.setTotal(page.getTotal()); 179 | result.setCurrent(page.getCurrent()); 180 | result.setSize(page.getSize()); 181 | result.setRecords(list); 182 | return result; 183 | } 184 | } 185 | ``` 186 | ```java 187 | @ExcelHandle 188 | public class UserImportHandler implements ImportHandler { 189 | 190 | @Autowired 191 | IUserService userService; 192 | 193 | @Override 194 | public List importData(List list, DataImportParam dataImportParam) 195 | throws Exception { 196 | List errorList=new ArrayList<>(); 197 | List saveUsers=new ArrayList<>(); 198 | for (UserImportModel userImportModel : list) { 199 | if (userImportModel.getMobile().contains("00000000")){ 200 | ErrorMsg msg = new ErrorMsg(userImportModel.getRow(), "手机号包含太多0"); 201 | errorList.add(msg); 202 | }else{ 203 | BeanCopier beanCopier = BeanCopier.create(UserImportModel.class, User.class, false); 204 | User user = new User(); 205 | beanCopier.copy(userImportModel,user,null); 206 | saveUsers.add(user); 207 | } 208 | } 209 | userService.saveBatch(saveUsers); 210 | return errorList; 211 | } 212 | } 213 | ``` 214 | 编写前端页面 215 | ![按钮](https://github.com/2229499815/async-excel/blob/master/doc/main.png) 216 | ![查看任务](https://github.com/2229499815/async-excel/blob/master/doc/viewtask.png) 217 | ### 高级功能 218 | #### 自定义存储 219 | 假如你已经对接好了第三方的存储比如oss、cos,七牛云存储等 220 | 你只需要在你的项目中实现IStorageService 接口即可 221 | 友情链接:存储可以引入!梦想大佬的sdk【[spring-file-storage](https://github.com/1171736840/spring-file-storage)】 全平台支持 222 | ```java 223 | @Component 224 | public class CosStorageService implements IStorageService { 225 | @Autowired 226 | private CosClient cosClient; 227 | 228 | @Override 229 | public String write(String name, Consumer osConsumer) throws Exception { 230 | return null; 231 | } 232 | //实现此方法即可 233 | @Override 234 | public String write(String name, InputStream data) throws Exception { 235 | String url = cosClient.upload(name,data); 236 | return url; 237 | } 238 | 239 | @Override 240 | public InputStream read(String path) throws Exception { 241 | return null; 242 | } 243 | 244 | @Override 245 | public boolean delete(String path) throws Exception { 246 | return false; 247 | } 248 | } 249 | ``` 250 | #### 自定义线程池 251 | 你可以直接使用spring的线程池,如果你需要传入你系统自定义的上下文你只需要做如下配置即可 252 | 前提是你已经定义好spring的线程池并填充好上下文装饰器ContextDecorator,线程装饰器的目的是为了将主线程的上下文传递给子线程 253 | ```java 254 | @Configuration 255 | @EnableAsync() 256 | public class ContextDecoratorThreadPoolConfiguration { 257 | 258 | @Bean 259 | ThreadPoolTaskExecutor threadPoolTaskExecutor(){ 260 | int core = Runtime.getRuntime().availableProcessors(); 261 | if (core<2){ 262 | core=2; 263 | } 264 | int corePool=core*2; 265 | int maxPool=core*10; 266 | 267 | ThreadPoolTaskExecutor threadPoolTaskExecutor=new ThreadPoolTaskExecutor(); 268 | //核心线程数 269 | threadPoolTaskExecutor.setCorePoolSize(corePool); 270 | //最大线程数 271 | threadPoolTaskExecutor.setMaxPoolSize(maxPool); 272 | //设置线程装饰器 273 | threadPoolTaskExecutor.setTaskDecorator(new ContextDecorator()); 274 | //设置阻塞队列容量 275 | threadPoolTaskExecutor.setQueueCapacity(1000); 276 | //设置线程前缀 277 | threadPoolTaskExecutor.setThreadNamePrefix("system-context-async-"); 278 | threadPoolTaskExecutor.initialize(); 279 | return threadPoolTaskExecutor; 280 | } 281 | 282 | } 283 | ``` 284 | 配置线程池,如果你未配置系统将声明一个默认的线程池DefaultThreadPoolConfiguration 285 | ```java 286 | @Configuration 287 | @AutoConfigureBefore(ExcelAutoConfiguration.class) 288 | @ConditionalOnClass(ExcelThreadPool.class) 289 | public class AsyncExcelConfiguration { 290 | 291 | @Bean 292 | public ExcelThreadPool excelThreadPool(ThreadPoolTaskExecutor threadPoolTaskExecutor){ 293 | return new ExcelThreadPool(threadPoolTaskExecutor.getThreadPoolExecutor()); 294 | } 295 | 296 | } 297 | ``` 298 | 299 | #### 导入单页处理场景 300 | 此时我们可以开启最大行数校验 301 | dataImportParam.setMaxRows=1000; 302 | dataImportParam.setValidMaxRows=true; 303 | dataImportParam.setBatchSize=1000; 304 | 这样就变成单页处理。 305 | 306 | #### 导出动态表头 307 | dataExportParam.setDynamicHead=true; 308 | 此时我们需要传入一个动态表头 309 | dataExportParam.setHeadList=list>; 310 | 311 | #### 导出自定义样式 312 | dataExportParam.setWriteHandlers=List 313 | 314 | #### 权限隔离 315 | 表中内置三个权限隔离字段 316 | + `tenant_code` '租户编码' 317 | + `create_user_code` '用户编码' 318 | + `business_code` '业务编码' 319 | 可以在插入数据时进行带入,如果你声明了自定义线程池,可以从系统上下文读取对应字段设置进去。 320 | DataParam.setxxx 系统将会默认插入数据库,不用在进行特殊处理。后续查询时你想根据什么维度查询都可以,businessCode用于区分不同的业务模块比如用户模块,订单模块可以定一个枚举进行区分 321 | 权限可以隔离到用户,也可以隔离到租户,根据你系统的要求自行定义。使用ExcelService.listPage进行数据查询。当然你也可以自定义接口的方式根据你喜欢的方式进行查询 322 | 323 | #### 更新日志 324 | 建议使用最新版本 325 | 326 | 1.1.2版本 327 | #bug修复 328 | 329 | 1.1.1版本 330 | #添加多sheet导出支持 331 | #添加动态表头导入 332 | #添加动态表头导出 333 | #重构导入导出将writeSheet移动至handler中在init阶段进行定义。 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /async-excel-core/src/main/java/com/asyncexcel/core/importer/AsyncImportTaskSupport.java: -------------------------------------------------------------------------------- 1 | package com.asyncexcel.core.importer; 2 | 3 | import com.alibaba.excel.EasyExcel; 4 | import com.alibaba.excel.ExcelWriter; 5 | import com.alibaba.excel.enums.CellDataTypeEnum; 6 | import com.alibaba.excel.metadata.Cell; 7 | import com.alibaba.excel.metadata.data.ReadCellData; 8 | import com.alibaba.excel.support.ExcelTypeEnum; 9 | import com.alibaba.excel.write.builder.ExcelWriterBuilder; 10 | import com.alibaba.excel.write.metadata.WriteSheet; 11 | import com.asyncexcel.core.ErrorMsg; 12 | import com.asyncexcel.core.ExceptionUtil; 13 | import com.asyncexcel.core.ImportRow; 14 | import com.asyncexcel.core.ImportRowMap; 15 | import com.asyncexcel.core.model.ExcelTask; 16 | import com.asyncexcel.core.service.IStorageService; 17 | import com.asyncexcel.core.service.TaskService; 18 | import java.io.IOException; 19 | import java.io.PipedInputStream; 20 | import java.io.PipedOutputStream; 21 | import java.time.LocalDateTime; 22 | import java.time.format.DateTimeFormatter; 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.Map.Entry; 29 | import java.util.Set; 30 | import java.util.concurrent.FutureTask; 31 | import org.apache.commons.collections4.CollectionUtils; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | /** 36 | * @Description 处理出错的数据,单独开个excel写入数据并返回一个文件地址提供下载 37 | * @Author 姚仲杰#80998699 38 | * @Date 2022/7/12 14:49 39 | */ 40 | public class AsyncImportTaskSupport implements ImportSupport { 41 | 42 | private final static Logger log = LoggerFactory.getLogger(AsyncImportTaskSupport.class); 43 | IStorageService storageService; 44 | TaskService taskService; 45 | public static String IMPORT_ERROR_PREFIX = "import-error-"; 46 | public static String XLSX_SUFFIX = ".xlsx"; 47 | 48 | public AsyncImportTaskSupport(IStorageService storageService, 49 | TaskService taskService) { 50 | this.storageService = storageService; 51 | this.taskService = taskService; 52 | } 53 | 54 | @Override 55 | public ExcelTask createTask(DataImportParam param) { 56 | ExcelTask task = new ExcelTask(); 57 | task.setType(1); 58 | task.setStatus(0); 59 | task.setSourceFile(param.getSourceFile()); 60 | task.setFileName(param.getFilename()); 61 | task.setStartTime(LocalDateTime.now()); 62 | task.setTenantCode(param.getTenantCode()); 63 | task.setCreateUserCode(param.getCreateUserCode()); 64 | task.setBusinessCode(param.getBusinessCode()); 65 | taskService.save(task); 66 | return task; 67 | } 68 | 69 | @Override 70 | public void beforeImport() { 71 | 72 | } 73 | 74 | @Override 75 | public void onImport(ImportContext ctx) { 76 | ExcelTask excelTask = ctx.getTask(); 77 | excelTask.setStatus(1); 78 | excelTask.setFailedCount(ctx.getFailCount()); 79 | excelTask.setSuccessCount(ctx.getSuccessCount()); 80 | excelTask.setTotalCount(ctx.getTotalCount()); 81 | taskService.updateById(excelTask); 82 | } 83 | 84 | /**这段逻辑过于复杂,后续需要进行抽象重构 85 | * @param dataList 86 | * @param ctx 87 | * @param errorMsgList 88 | */ 89 | @Override 90 | public void onWrite(Collection dataList, ImportContext ctx, 91 | List errorMsgList) { 92 | //行数据不能为空 93 | if (CollectionUtils.isEmpty(dataList)) { 94 | return; 95 | } 96 | //最终写出的数据行 97 | List errorDataList = new ArrayList<>(); 98 | Map map = new HashMap<>(dataList.size()); 99 | //将数据行转map,行号为key遍历数据行,两种情况 100 | // 一种是格式错误,非业务错误,此时errorMsgList没有数据 101 | // 第二种是业务错误,此时错误消息有数据,这里又分为两种情况,一种是普通对象类型的,另一种是importRowMap类型的 102 | for (Object o : dataList) { 103 | if (o instanceof ImportRow) { 104 | ImportRow o1 = (ImportRow) o; 105 | map.put(o1.getRow(), o); 106 | } else { 107 | errorDataList.add(o); 108 | } 109 | } 110 | //处理上述第二种情况,将错误消息与对应行号进行匹配 111 | if (errorDataList.size() == 0 && errorMsgList != null && errorMsgList.size() > 0) { 112 | //如果是ImportRowMap再次进行处理 113 | if (ImportRowMap.class.isAssignableFrom(ctx.getErrorHeadClass())) { 114 | for (ErrorMsg errorMsg : errorMsgList) { 115 | Object o = map.get(errorMsg.getRow()); 116 | if (o instanceof ImportRowMap) { 117 | ImportRowMap o1 = (ImportRowMap) o; 118 | o1.setRowFailMessage(errorMsg.getMsg()); 119 | Map dataMap = o1.getDataMap(); 120 | Set> entries = dataMap.entrySet(); 121 | List cellDatas=new ArrayList(); 122 | for (Entry entry : entries) { 123 | ReadCellData readCellData =(ReadCellData) entry.getValue(); 124 | CellDataTypeEnum type = readCellData.getType(); 125 | Object value; 126 | switch (type){ 127 | case EMPTY: 128 | case STRING: 129 | value=readCellData.getStringValue(); 130 | break; 131 | case NUMBER: 132 | value=readCellData.getNumberValue(); 133 | break; 134 | case BOOLEAN: 135 | value=readCellData.getBooleanValue(); 136 | break; 137 | case DATE: 138 | value=readCellData.getDataFormatData(); 139 | break; 140 | default: 141 | throw new IllegalStateException("Cannot set values now"); 142 | } 143 | cellDatas.add(value); 144 | } 145 | //数据对齐表头,避免最后一列为空时,错误信息的列被挤到上一列 146 | Map headMap= o1.getHeadMap(); 147 | int size = headMap.size(); 148 | int dataSize = cellDatas.size(); 149 | if (dataSize < size) { 150 | for (int i = 0; i < size - dataSize; i++) { 151 | cellDatas.add(null); 152 | } 153 | } 154 | //添加错误消息列 155 | cellDatas.add(((ImportRowMap) o).getRowFailMessage()); 156 | cellDatas.add(((ImportRowMap) o).getRow()); 157 | errorDataList.add(cellDatas); 158 | }else { 159 | errorDataList.add(o); 160 | } 161 | } 162 | }else { 163 | for (ErrorMsg errorMsg : errorMsgList) { 164 | Object o = map.get(errorMsg.getRow()); 165 | if (o instanceof ImportRow) { 166 | ImportRow o1 = (ImportRow) o; 167 | o1.setRowFailMessage(errorMsg.getMsg()); 168 | } 169 | errorDataList.add(o); 170 | } 171 | } 172 | } 173 | if (errorDataList.size() == 0) { 174 | return; 175 | } 176 | 177 | if (ctx.getOutputStream() == null) { 178 | PipedOutputStream pos = new PipedOutputStream(); 179 | try { 180 | PipedInputStream pis = new PipedInputStream(pos); 181 | ctx.setInputStream(pis); 182 | StringBuilder sb = new StringBuilder(); 183 | sb.append(IMPORT_ERROR_PREFIX) 184 | .append(ctx.getFileName()) 185 | .append(LocalDateTime.now() 186 | .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))) 187 | .append(XLSX_SUFFIX); 188 | final String errFileName = sb.toString(); 189 | //此处单独起线程避免线程互相等待死锁 190 | FutureTask futureTask = new FutureTask<>( 191 | () -> storageService.write(errFileName, ctx.getInputStream())); 192 | new Thread(futureTask).start(); 193 | ctx.setFuture(futureTask); 194 | } catch (IOException e) { 195 | ExceptionUtil.wrap2Runtime(e); 196 | } 197 | ctx.setOutputStream(pos); 198 | } 199 | if (ctx.getExcelWriter() == null) { 200 | ExcelWriterBuilder builder = EasyExcel.write(ctx.getOutputStream()) 201 | .excelType(ExcelTypeEnum.XLSX).autoCloseStream(false); 202 | //判断是否是ImportRowMap的子类,如果是那么进行表头转换处理 203 | if (ImportRowMap.class.isAssignableFrom(ctx.getErrorHeadClass())) { 204 | List> headList=new ArrayList<>(); 205 | for (Object o : dataList) { 206 | ImportRowMap rowMap = (ImportRowMap) o; 207 | Set> heads = rowMap.getHeadMap().entrySet(); 208 | for (Entry head : heads) { 209 | List headCell = new ArrayList<>(); 210 | headCell.add(head.getValue()); 211 | headList.add(headCell); 212 | } 213 | break; 214 | } 215 | List row = new ArrayList<>(); 216 | List failMsg=new ArrayList<>(); 217 | row.add(SheetConst.FAIL_ROW_TITLE); 218 | failMsg.add(SheetConst.FAIL_MSG_TITLE); 219 | headList.add(failMsg); 220 | headList.add(row); 221 | builder.registerWriteHandler(new FailMsgWriteHandler()); 222 | builder.head(headList); 223 | }else{ 224 | //如果不是那么直接设置 225 | builder.head(ctx.getErrorHeadClass()); 226 | } 227 | ExcelWriter excelWriter = builder.build(); 228 | WriteSheet writeSheet = EasyExcel.writerSheet(0).sheetName(ctx.getSheetName()).build(); 229 | ctx.setWriteSheet(writeSheet); 230 | ctx.setExcelWriter(excelWriter); 231 | } 232 | ctx.getExcelWriter().write(errorDataList, ctx.getWriteSheet()); 233 | } 234 | 235 | @Override 236 | public void onError(ImportContext ctx) { 237 | close(ctx); 238 | ExcelTask excelTask = ctx.getTask(); 239 | excelTask.setStatus(3); 240 | excelTask.setFailedCount(ctx.getFailCount()); 241 | excelTask.setSuccessCount(ctx.getSuccessCount()); 242 | excelTask.setEndTime(LocalDateTime.now()); 243 | excelTask.setTotalCount(ctx.getTotalCount()); 244 | excelTask.setFailedFileUrl(ctx.getErrorFile()); 245 | excelTask.setFailedMessage(ctx.getFailMessage()); 246 | taskService.updateById(excelTask); 247 | if (log.isDebugEnabled()) { 248 | log.debug("task import error"); 249 | } 250 | } 251 | 252 | public void close(ImportContext ctx) { 253 | if (ctx.getExcelWriter() != null) { 254 | ctx.getExcelWriter().finish(); 255 | } 256 | if (ctx.getOutputStream() != null) { 257 | try { 258 | ctx.getOutputStream().close(); 259 | } catch (Exception e) { 260 | e.printStackTrace(); 261 | } 262 | } 263 | if (ctx.getInputStream() != null) { 264 | try { 265 | if (ctx.getFuture() != null) { 266 | ctx.setErrorFile(ctx.getFuture().get()); 267 | } 268 | ctx.getInputStream().close(); 269 | } catch (Exception e) { 270 | e.printStackTrace(); 271 | } 272 | } 273 | } 274 | 275 | @Override 276 | public void onComplete(ImportContext ctx) { 277 | close(ctx); 278 | ExcelTask excelTask = ctx.getTask(); 279 | excelTask.setStatus(2); 280 | excelTask.setFailedCount(ctx.getFailCount()); 281 | excelTask.setSuccessCount(ctx.getSuccessCount()); 282 | excelTask.setEndTime(LocalDateTime.now()); 283 | excelTask.setTotalCount(ctx.getTotalCount()); 284 | excelTask.setFailedFileUrl(ctx.getErrorFile()); 285 | taskService.updateById(excelTask); 286 | if (log.isDebugEnabled()) { 287 | log.debug("task completed"); 288 | } 289 | } 290 | 291 | } 292 | --------------------------------------------------------------------------------