報表的操作(一)

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