报表的操作(一)

easyexcel入门

easyexcel工具类,github地址:https://github.com/alibaba/easyexcel

一、gitHub摘抄,上面demo都有
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。

二、使用easyexcel工具+sql动态读取,生成复杂一点的数字报表

1、入口

package com.xxx.xxxx.tool.execl.controller;

import com.alibaba.excel.support.ExcelTypeEnum;
import com.xxx.xxxx.tool.execl.util.ExcelReportTool;
import com.xxx.xxxx.tool.execl.util.SendMailUtil;
import com.xxx.xxxx.tool.execl.util.SqlUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.time.LocalDateTime;

/**
 * @author xxx
 */
@Slf4j
@Configurable
@EnableScheduling
@RestController
@RequestMapping("/report")
public class ExcelReportToolTime {

  private static SqlUtil sqlUtil;

  @Autowired
  private SendMailUtil sendMailUtil;

  private static String urlFile="jdbc:mysql://10.1.5.75:3306/xxxx";
  private static String nameFile="xxxx";
  private static String passwordFile="xxxx";
  private static String thirdFile="xxxx-tools/xxxx-tools-xlsxReportEngine/src/main/resources/input/sql_template_31.xlsx";
  private static String FourthFile="xxxx-tools/xxxx-tools-xlsxReportEngine/src/main/resources/output/result.xls";
  private static String fifthFile="xxxx-tools/xxxx-tools-xlsxReportEngine/src/main/resources/input/export_template_31.xls";
  /**
   * //@param args 0-数据库连接url 1-账户 2-密码 3-导出sql脚本模板 4-导出文件地址 5-导出填充模板文件
   * */
  public void mainExport() {//String[] args
    try (FileInputStream exportRuleTemplateFile = new FileInputStream(thirdFile);
      FileOutputStream exportFile = new FileOutputStream(FourthFile)) {
      createSqlUtil(urlFile, nameFile, passwordFile);
      ExcelReportTool excelReportTool = new ExcelReportTool();
      excelReportTool.setSqlUtil(sqlUtil);
      if (thirdFile.toUpperCase().endsWith("XLS")) {
        excelReportTool.exportExcel(fifthFile, exportRuleTemplateFile, exportFile, ExcelTypeEnum.XLS);
      }
      if (thirdFile.toUpperCase().endsWith("XLSX")) {
        excelReportTool.exportExcel(fifthFile, exportRuleTemplateFile, exportFile, ExcelTypeEnum.XLSX);
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        sqlUtil.closeConnection();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }

  private static void createSqlUtil(String url, String username, String password) throws SQLException{
    sqlUtil = new SqlUtil(url, username, password);
  }

  @PostMapping(path = "/excelReportDay")
  public String excelReportDay( @RequestBody String requestMessage, HttpServletRequest request) {
    System.out.println(requestMessage);
    mainExport();
    return "success";
  }

  /**
   * 直接指定时间间隔,例如:每天15:09
   * "0 10 0 1 * ?"  每月1号的0:10:00执行
   * "0 15 10 L * ?" 每月最后一日的上午10:15触发
   */
  @Scheduled(cron = "0 13 17 * * ?")
  private void configureTasks() {
    log.info("输出报表数据,执行静态定时任务时间:{}", LocalDateTime.now());
    mainExport();
    //发送邮件
    sendMailAttach();
  }

  private void sendMailAttach(){
    String to="[email protected]";
    String subject="subject";
    String content="2020月份优惠券统计日报,请查收";
    String filePath=FourthFile;
    //SendMailUtil sendMailUtil = new SendMailUtil();
    sendMailUtil.sendAttachmentMail(to,subject,content,filePath);
  }
}


2、使用的工具类

package com.xxxx.xxxx.tool.execl.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.xxxx.xxxx.tool.execl.bean.CommandRuleEnum;
import com.xxxx.xxxx.tool.execl.bean.CommandTypeEnum;
import com.xxxx.xxxx.tool.execl.bean.DataType;
import com.xxxx.xxxx.tool.execl.bean.InputRowModel;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Setter
public class ExcelReportTool {

  private SqlUtil sqlUtil;

  private LinkedBlockingQueue linkedBlockingQueue;

  private ThreadPoolExecutor threadPool;

  /**
   * @param templateFile 导出填充模板
   * @param exportRuleTemplateFile 导出脚本模板
   * @param exportFile 导出目标文件
   * @param excelType excel类型 xls xlsx
   * */
  public void exportExcel(String templateFile, FileInputStream exportRuleTemplateFile, FileOutputStream exportFile, ExcelTypeEnum excelType) {
    linkedBlockingQueue = new LinkedBlockingQueue<Runnable>();
    threadPool= new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, linkedBlockingQueue);
    try {
      ExcelReaderBuilder excelReaderBuilder = EasyExcel.read(exportRuleTemplateFile, InputRowModel.class, null);
      excelReaderBuilder.excelType(excelType);
      List<InputRowModel> list = excelReaderBuilder.sheet(0).doReadSync();
      // 合并命令
      List<InputRowModel> mergeCommand = new LinkedList<>();
      // 待导出list数据
      Map<String, List<Map<String, String>>> listData = new ConcurrentHashMap<>();
      // 待导出map数据(多条sql结果不可有重复字符)
      Map<String, String> mapData = new HashMap<>();
      if (!CollectionUtils.isEmpty(list)) {
        for (InputRowModel row: list) {
          if (row.getCommand().startsWith(CommandTypeEnum.SQL.getType())) {
            exceSql(listData, mapData, row);
            continue;
          }
          if (row.getCommand().startsWith(CommandTypeEnum.MERGE.getType())) {
            mergeCommand.add(row);
          }
        }
      }
      // 等待子线程执行完毕
      threadPool.shutdown();
      while (!threadPool.isTerminated()) {
        Thread.sleep(1000);
      }
      export(batchExceMerge(mergeCommand, listData), mapData, exportFile, templateFile);
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      try {
        sqlUtil.closeConnection();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }

  private void exceSql(Map<String, List<Map<String, String>>> listData, Map<String, String> mapData, InputRowModel row) {
    threadPool.execute(new Runnable() {
      @Override
      public void run() {
        String sql = row.getCommand().replaceAll(CommandTypeEnum.SQL.getType(),"");
        try {
          if (row.getType().equals(DataType.LIST.getType())) {
            // 查询多条输出List<Map<String, String>>
            listData.put(row.getAlias(), sqlUtil.queryList(sql));
          }
          if (row.getType().equals(DataType.MAP.getType())) {
            // 查询单条输出Map<String, String>
            mapData.putAll(sqlUtil.queryMap(sql));
          }
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    });
  }

  /**
   * 导出
   * @param exportListResults 带到处的list数据
   * @param exportMapResults 待导出的map数据
   * @param exportPath 导出文件流
   * */
  private void export(Map<String, List<Map<String, String>>> exportListResults, Map<String, String> exportMapResults, FileOutputStream exportPath, String templateFile) {
    try  {
      ExcelWriter excelWriter = EasyExcel.write(exportPath).withTemplate(templateFile).build();
      WriteSheet writeSheet = EasyExcel.writerSheet().build();
      // 填充list列表
      Iterator<Map.Entry<String, List<Map<String, String>>>> iterator = exportListResults.entrySet().iterator();
      while (iterator.hasNext()) {
        Map.Entry<String, List<Map<String, String>>> row = iterator.next();
        excelWriter.fill(new FillWrapper(row.getKey(), row.getValue()), writeSheet);
      }
      // 填充map
      if(0<exportMapResults.size()){
        excelWriter.fill(exportMapResults, writeSheet);
      }
      excelWriter.finish();
      exportPath.flush();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * sql执行结束后执行合并命令
   * @param mergeCommand 待执行合并命令
   * @param data 待合并数据
   * */
  private Map<String, List<Map<String, String>>> batchExceMerge(List<InputRowModel> mergeCommand, Map<String, List<Map<String, String>>> data) {
    Map<String, List<Map<String, String>>> exportResults = new HashMap<>();
    String command;
    for (InputRowModel row: mergeCommand) {
      command = row.getCommand().replaceAll(CommandTypeEnum.MERGE.getType(),"");
      for (CommandRuleEnum value : CommandRuleEnum.values()) {
        if (Pattern.compile(value.getRule()).matcher(command).find()) {
          List<Map<String, String>> merge = exec(value, command, data);
          // 合并后数据存入待导出数据
          exportResults.put(row.getAlias(), merge);
          // 合并后数据存入待合并数据(别的合并命令可能用上?)
          data.put(row.getAlias(), merge);
          break;
        }
      }
    }
    return exportResults;
  }

  /**
   * 执行合并命令
   * @param commandRuleEnum 合并命令类型
   * @param command 合并命令
   * @param data 待合并数据
   * */
  private List<Map<String, String>> exec(CommandRuleEnum commandRuleEnum, String command, Map<String, List<Map<String, String>>> data) {
    List<Map<String, String>> result = null;
    switch (commandRuleEnum.getOperation()) {
      // CommandRuleEnum.MERGE_BY_KEY 根据指定字段匹配合并
      case 1:
        String[] commands = command.split("=");
        result = mergeByKey(data, commands[0], commands[1].split(","));
        break;
      // CommandRuleEnum.MERGE 直接合并
      case 2:
        // TODO
        ;break;
    }
    return result;
  }

  /**
   * 根据指定字段匹配合并
   * @param data 待合并数据
   * @param key 指定匹配字段
   * @param targetData 命令中指定需要合并的数据别名
   */
  private List<Map<String, String>> mergeByKey(Map<String, List<Map<String, String>>> data, String key, String[] targetData) {
    List<Map<String, String>> mergeResult = new LinkedList<>();
    Map<String, Map<String, String>> equalList=new LinkedHashMap<>();
    // 根据别名取出下标为0的数据整理成K/V map(以指定匹配字段值做Key),便于匹配合并
    data.get(targetData[0]).forEach(x -> {
      equalList.put(x.get(key), x);
    });
    // 从下标1开始时遍历与equalList中数据进行匹配合并
    for (int i = 1;i<targetData.length;i++) {
      for (int j = 0;j<data.get(targetData[i]).size();j++) {
        if (equalList.containsKey(data.get(targetData[i]).get(j).get(key))) {
          // 找到匹配数据合并
          equalList.get(data.get(targetData[i]).get(j).get(key)).putAll(data.get(targetData[i]).get(j));
        } else {
          // 未找到匹配数据,新起一条写入
          equalList.put(data.get(targetData[i]).get(j).get(key), data.get(targetData[i]).get(j));
        }
      }
    }
    equalList.entrySet().forEach(x -> {
      mergeResult.add(x.getValue());
    });
    return mergeResult;
  }

}

数据库链接操作工具

package com.xxxx.xxxx.tool.execl.util;

import java.sql.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class SqlUtil {

  private Connection connection;

  public SqlUtil(String url, String username, String password) throws SQLException {
    connection = DriverManager.getConnection(url, username, password);
  }

  /**
   * 多行查询输出List<Map<String, String>>
   * */
  public List<Map<String, String>> queryList(String sql) throws SQLException {
    List<Map<String, String>> outResult = new LinkedList<>();
    Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery(sql);
    while (resultSet.next()) {
      Map<String, String> map = new HashMap<>();
      for (int i = 1;i <= resultSet.getMetaData().getColumnCount();i++) {
        map.put(resultSet.getMetaData().getColumnLabel(i), resultSet.getString(i));
      }
      outResult.add(map);
    }
    statement.close();
    return outResult;
  }

  /**
   * 查询单行输出Map<String, String>
   * */
  public Map<String, String> queryMap(String sql) throws SQLException {
    return queryList(sql).get(0);
  }

  public void closeConnection() throws SQLException {
    connection.close();
  }

}

3、基础类型数据小配

package com.xxxx.xxxx.tool.execl.bean;

import lombok.Getter;

/**
 * 合并命令类型
 * */
@Getter
public enum CommandRuleEnum {
  MERGE_BY_KEY("[0-9a-zA-Z]+[=][0-9a-zA-Z]+[,][0-9a-zA-Z]+",  1, "通过key匹配合并数据"),
  MERGE("([0-9a-zA-Z]+[,])+[0-9a-zA-Z]+", 2, "直接合并数据");

  private String rule;

  private Integer operation;

  private String remark;

  CommandRuleEnum(String rule, Integer operation, String remark) {
    this.rule = rule;
    this.operation = operation;
    this.remark = remark;
  }
}


package com.xxxx.xxxx.tool.execl.bean;

import lombok.Getter;

/**
 * 命令类型
 * */
@Getter
public enum CommandTypeEnum {

  SQL("sql:", "sql脚本"),
  MERGE("merge:", "合并导出");

  private String type;

  private String remark;

  CommandTypeEnum(String type, String remark) {
    this.type = type;
    this.remark = remark;
  }

}

package com.xxxx.xxxx.tool.execl.bean;

import lombok.Getter;

/**
 * sql输出数据类型
 * */
@Getter
public enum DataType {
  LIST("list"),
  MAP("map");

  private String type;

  DataType(String type) {
    this.type = type;
  }
}


package com.xxxx.xxxx.tool.execl.bean;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class InputRowModel {

  @ExcelProperty(value = "脚本", index = 0)
  private String command;

  @ExcelProperty(value = "类型", index = 1)
  private String type;

  @ExcelProperty(value = "别名", index = 2)
  private String alias;

  @ExcelProperty(value = "描述", index = 3)
  private String remark;

}


4、一个模板配置,一个脚本配置
在这里插入图片描述
脚本
在这里插入图片描述
在这里插入图片描述

5、xml的配置文件

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.xxx.xx</groupId>
		<artifactId>xxxx-tools</artifactId>
		<version>1.0.0.0</version>
	</parent>
	<artifactId>xxxx-tools-xlsxReportEngine</artifactId>

	<dependencies>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>2.2.4</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.13</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.12</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
		</dependency>
		<!-- 邮件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mail</artifactId>
			<version>2.2.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.6.0</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<transformers>
								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>com.aspire.execlreporttool.ExcelReportToolAppliaction</mainClass>
								</transformer>
							</transformers>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章