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