基于策略和模板方法模式的EasyExcel解析

862 阅读4分钟
解析接口
public interface IExcelParseService {
   /**
    * 通用excel解析(只针对单sheet页)
    * @param parseDataDto 待解析对象
    * @return ParseResult
   */
   ParseResult doExcelParse(ParseDataDto parseDataDto);

   /**
    * 业务类型,区分不同excel解析功能
    * @return string
    */
   String type();
}

抽象实现、采用模板方法模式

public abstract class AbstractExcelParseService<T, F> implements IExcelParseService {

   /**
    * Excel 解析分四步进行
    * 第一步: 解析Excel 数据
    * 第二步: 校验Excel 标题行校验码
    * 第三步: 数据校验和转换
    * 第四步: 数据入库
    */
   @Override
   public ParseResult doExcelParse(ParseDataDto parseDataDto) {
      if (parseDataDto == null) {
         log.error("参数: parseDataDto is null");
         return new ParseResult(ParseStatus.EXCEL_NOT_FOUND, "文件未找到 ");
      }
      // 获取公共文件路径
      String commonPath = parseDataDto.getCommPath();
      StringBuilder fullPath = new StringBuilder(commonPath);
      if (!parseDataDto.getFilePath().startsWith(File.separator)) {
         fullPath.append(File.separator);
      }
      fullPath.append(parseDataDto.getFilePath());
      InputStream inputStream;
      try {
         inputStream = new BufferedInputStream(new FileInputStream(fullPath.toString()));
      } catch (FileNotFoundException e) {
         log.error("文件" + parseDataDto.getId() + "未找到:" + fullPath);
         return new ParseResult(ParseStatus.EXCEL_NOT_FOUND, "文件未找到 ");
      }
      ExcelData<T> excelData;
      try {
         excelData = parseExcel(inputStream, getEntityClass());
      } catch (Exception e) {
         log.error("模板解析错误:" + e);
         return new ParseResult(ParseStatus.ERROR, "Excel解析错误");
      }

      ParseResult parseResult = headCheck(parseDataDto.getType(), excelData);
      if (parseResult.getParseStatus() != ParseStatus.SUCCESS) {
         return new ParseResult(ParseStatus.TEMPLATE_ERROR, parseResult.getMessage());
      }
      ExcelResult<F> excelResult = dataCheckAndTrans(excelData, parseDataDto);
      return saveData(excelResult, parseDataDto);
   }

   /**
    * 第一步: 解析Excel数据
    */
   protected <T> ExcelData<T> parseExcel(InputStream inputStream, Class<T> clazz) {
      return EasyExcelUtil.readExcel(inputStream, clazz);
   }


   /**
    * 获取对象类型
    *
    * @return Class
    */
   protected abstract Class<T> getEntityClass();

   /**
    * 第二步: 校验Excel 标题行和校验码
    *
    * @param type      业务类型
    * @param excelData 数据
    * @return ParseResult
    */
   protected abstract ParseResult headCheck(String type, ExcelData<T> excelData);

   /**
    * 第三步: 数据校验和转换
    */
   protected abstract ExcelResult<F> dataCheckAndTrans(ExcelData<T> excelData, ParseDataDto parseDataDto);

   /**
    * 第四步: 数据入库
    */
   protected abstract ParseResult saveData(ExcelResult<F> excelResult, ParseDataDto parseDataDto);

   /**
    * 去除表头最后的 * 号
    *
    * @param string 表头字段
    * @return String
    */
   public String wipeLastStar(String string) {
      if (StringUtils.equals(StringUtils.right(string, 1), "*")) {
         return StringUtils.left(string, string.length() - 1);
      }
      return string;
   }

   /**
    * 获取需要生成校验码的标题行并拼装成字符串,以逗号分割
    * 注意:需要把业务类别放在第一个
    *
    * @param type
    * @param headMap
    * @return
    */
   protected String getHeadString(String type, Map<Integer, String> headMap) {
      Collection<String> heads = headMap.values();
      return StringUtils.join(heads, ",");
   }
}
路由类,采用策略模式
@Primary
@Service
public class ExcelParseRouteServiceImpl implements IExcelParseService {

   private Map<String, IExcelParseService> parseServiceMap;

   @Autowired
   private List<IExcelParseService> parseServices;

   /**
    * 初始化类是 将IExcelParseService 的所有实现类放入map容器
    */
   @PostConstruct
   public void init() {
      parseServiceMap = Maps.newConcurrentMap();
      for (IExcelParseService excelParseService : parseServices) {
         parseServiceMap.put(excelParseService.type(), excelParseService);
      }
   }

   @Override
   public ParseResult doExcelParse(ParseDataDto parseDataDto) {
      if (parseDataDto == null) {
         return new ParseResult(ParseStatus.ERROR, "入参为空");
      }

      String type = parseDataDto.getType();
      IExcelParseService excelParseService = parseServiceMap.get(type);
      if (excelParseService == null) {
         log.error("业务类别:type = " + type + " 没有配置解析器");
         return new ParseResult(ParseStatus.ERROR, "没有配置Excel解析器");
      }
      return excelParseService.doExcelParse(parseDataDto);
   }

   @Override
   public String type() {
      return null;
   }
}
待解析对象
/**
 * 待解析对象
 */
@Data
public class ParseDataDto {

   private String id;
   private String type;
   private String commPath;
   private String filePath;
}
解析结果状态枚举类
/**
 * Excel 解析结果状态枚举类
 * 
 */
public enum ParseStatus {

   /**
    * 未处理
    */
   UNPROCESSED("00"),
   /**
    * 正在解析
    */
   PROCESSING("01"),
   /**
    * 成功
    */
   SUCCESS("11"),
   /**
    * 解析成功,部分数据有误,请下载模板文件查看错误详情
    */
   DATA_ERROR("12"),
   /**
    * 处理失败
    */
   ERROR("21"),
   /**
    * 不符合模板
    */
   TEMPLATE_ERROR("22"),
   /**
    * 文件未找到
    */
   EXCEL_NOT_FOUND("23");


   private final String code;
   ParseStatus(String code){
      this.code=code;
   }

   public String getCode(){
      return code;
   }
}
解析结果类

/**
 * 导入处理结果
 *
 */
@Data
public class ParseResult {

   private ParseStatus parseStatus;
   private int successCount;
   private int errorCount;
   private int count;
   private String message;

   public ParseResult(ParseStatus parseStatus) {
      this.parseStatus = parseStatus;
   }

   public ParseResult(ParseStatus parseStatus, String message) {
      this.parseStatus = parseStatus;
      this.message = message;
   }
}
解析excel的返回值
/**
 * 解析excel的返回值
 * 
 */
@Data
public class ExcelResult<T> {

   private int count;
   private String message;
   private List<T> successData;
   private List<T> errorData;

}
excel 数据封装类
/**
 * 解析后的excel 数据封装类
 */
@Data
public class ExcelData<T> {
   private List<T> dataList;
   private Map<Integer, String> headMap;
}
excel 解析监听类
/**
 * excel 解析监听类
 */
@Slf4j
public class ExcelDataListener<T> extends AnalysisEventListener<T> {

   private final List<T> dataList = new ArrayList<>(2000);
   private Map<Integer, String> headMap;

   public Map<Integer, String> getHeadMap() {
      return headMap;
   }

   public List<T> getDataList() {
      return dataList;
   }

   @Override
   public void invoke(T t, AnalysisContext analysisContext) {
      dataList.add(t);
   }

   @Override
   public void doAfterAllAnalysed(AnalysisContext analysisContext) {

   }

   /**
    * 获取表头信息
    *
    * @param headMap 表头map
    * @param context context
    */
   @Override
   public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
      this.headMap = headMap;
   }
}
EasyExcelUtil 工具类
public class EasyExcelUtil {

   /**
    * 获取excel表头
    * @param heads 标题数组
    * @return excel easyExcel标题数组
   */
   public static List<List<String>> getHeadList(String... heads) {
      List<List<String>> headList = new ArrayList<>(heads.length);
      for (String head : heads) {
         headList.add(Lists.newArrayList(head)) ;
      }
      return headList;
   }

   /**
    * 读Excel 返回list 数据
    * @param filePath excel 路径
    * @return clazz 映射类
   */
   public static <T> List<T> read(String filePath, Class<T> clazz) {
      ExcelDataListener<T> dataListener = new ExcelDataListener<T>();
      EasyExcel.read(filePath, clazz, dataListener).sheet().doRead();
      return dataListener.getDataList();
   }

   /**
    * 读Excel 返回list 数据
    * @param inputStream excel流
    * @return clazz 映射类
    */
   public static <T> List<T> read(InputStream inputStream, Class<T> clazz) {
      ExcelDataListener<T> dataListener = new ExcelDataListener<T>();
      EasyExcel.read(inputStream, clazz, dataListener).sheet().doRead();
      return dataListener.getDataList();
   }

   /**
    * 读Excel 返回list 数据和表头数据
    * @param filePath excel 路径
    * @return clazz 映射类
    */
   public static <T> ExcelData<T> readExcel(String filePath, Class<T> clazz) {
      ExcelDataListener<T> dataListener = new ExcelDataListener<T>();
      EasyExcel.read(filePath, clazz, dataListener).sheet().doRead();
      ExcelData<T> excelData = new ExcelData<>();
      excelData.setDataList(dataListener.getDataList());
      excelData.setHeadMap(dataListener.getHeadMap());
      return excelData;
   }

   /**
    * 读Excel 返回list 数据和表头数据
    * @param inputStream excel流
    * @return clazz 映射类
    */
   public static <T> ExcelData<T> readExcel(InputStream inputStream, Class<T> clazz) {
      ExcelDataListener<T> dataListener = new ExcelDataListener<T>();
      EasyExcel.read(inputStream, clazz, dataListener).sheet().doRead();
      ExcelData<T> excelData = new ExcelData<>();
      excelData.setDataList(dataListener.getDataList());
      excelData.setHeadMap(dataListener.getHeadMap());
      return excelData;
   }

   /**
    * web 导出excel
    * @param response response
    * @param excelName excel名
    * @param sheetName sheet名
    * @param clazz 映射类
    * @param dataList 数据
   */
   public static <T> void export(HttpServletResponse response, String excelName, String sheetName,
                      Class<T> clazz, List<T> dataList) throws IOException {
      response.setContentType("application/vnd.ms-excel");
      response.setCharacterEncoding("utf-8");
      // 这里URLEncoder.encode可以防止中文乱码
      excelName = URLEncoder.encode(excelName, "UTF-8");
      response.setHeader("Content-disposition", "attachment;filename=" + excelName + ".xlsx");
      EasyExcel.write(response.getOutputStream(), clazz)
            .sheet(sheetName).doWrite(dataList);
   }

   /**
    * web 导出excel
    * @param response response
    * @param excelName excel名
    * @param sheetName sheet名
    * @param List<List<String>> headList
    * @param dataList 数据
    */
   public static <T> void export(HttpServletResponse response, String excelName, String sheetName,
                                 List<List<String>> headList, List<T> dataList) throws IOException {
      response.setContentType("application/vnd.ms-excel");
      response.setCharacterEncoding("utf-8");
      // 这里URLEncoder.encode可以防止中文乱码
      excelName = URLEncoder.encode(excelName, "UTF-8");
      response.setHeader("Content-disposition", "attachment;filename=" + excelName + ".xlsx");
      EasyExcel.write(response.getOutputStream()).head(headList)
            .sheet(sheetName).doWrite(dataList);
   }
}

具体业务类只需要继承抽象实现、实现具体类即可