目錄
源碼地址 https://github.com/fzmeng/Excelbuild
-
建造者模式(Builder Pattern)簡單介紹:
-
將一個複雜對象的構建和它的表示分離,使同樣的構建構成可以創建不同的表示。
建造者模式也是屬於創建型模型,是23種GOF之一。
-
-
常見Excel導出場景
- 已確定數據源List<Object> 或者 List<Map<String,Object>> 情況(實體類型,數據量大小),瀏覽器直接導出文件流到本地 或 儲存至服務器某目錄下;
- 未確定數據源List<Object> 或者 List<Map<String,Object>> 情況(實體類型,數據量大小,是否分頁),但可知數據源獲取方式(SQL查詢,分頁數據請求接口等 ) ,瀏覽器直接導出文件流到本地 或 儲存至服務器某目錄下;
-
常見Excel 導出編碼過程-結合過程查看代碼
- 創建工作簿 HSSFWorkbook, XSSFWorkbook ,SXSSFWorkbook(本文使用此工作簿,它採用滑動窗口模式,避免OOM)
- 創建Sheet 表;
- 創建列寬;
- 創建樣式 - AbstractBuildStyle;
- 創建標題與表頭 - AbstractBuildTitle;
- 生成數據內容 - AbstractBuildContent;
-
本文中的Excel導出使用方式
-
package com.mfz.study.util.build; import com.alibaba.fastjson.JSONObject; import com.mfz.study.util.InvokeDataSource; import lombok.Data; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; /** * @Description: * @Author mengfanzhu * @Date 2019/12/18 14:19 * @Version 1.0 */ @RestController public class TestController { static List<ExcelExportModel> excelExportModels = new ArrayList<>(); static List<LinkedHashMap<String, Object>> excelExportMaps = new ArrayList<>(); static LinkedHashMap<String, Integer> columnTypeMap = new LinkedHashMap<String, Integer>() {{ put("id", ExcelCellType.STRING.getCode()); put("name", ExcelCellType.STRING.getCode()); put("address", ExcelCellType.STRING.getCode()); }}; static LinkedHashMap<String, String> titleMap = new LinkedHashMap<String, String>() {{ put("id", "標識"); put("name", "名字"); put("address", "地址"); }}; static { int max = 10000; for (int i = 0; i < max; i++) { ExcelExportModel excelExportModel = new ExcelExportModel(); excelExportModel.setName("黎明" + i); excelExportModel.setAddress("地址" + i); excelExportModel.setId(i + ""); excelExportModels.add(excelExportModel); } for (int i = 0; i < max; i++) { LinkedHashMap<String, Object> d = new LinkedHashMap<>(); d.put("name", "黎明" + i); d.put("address", "北京朝陽" + i); d.put("id", "00001" + i); excelExportMaps.add(d); } } @Data public static class ExcelExportModel { private String id; private String name; private String address; } @GetMapping(value = "/export") public void export(HttpServletResponse response) { useWay1(response); } @GetMapping(value = "/export3") public void export3(HttpServletResponse response) { useWay3(response); } @GetMapping(value = "/export4") public void export4(HttpServletResponse response) { useWay4(response); } /** * invoke data * * @param response */ private void useWay4(HttpServletResponse response) { ExcelExportModel param = new ExcelExportModel(); param.setId("10"); param.setName("我是反射獲取數據源"); param.setAddress("反射類地址"); new ExcelDirector() .addFieldTitleTable(titleMap) .setExcelFileName("反射獲取數據源文件.xlsx") .setColumnTypeMap(columnTypeMap) .setBoundInvokeClass(InvokeDataSource.class) .setBoundInvokeMethodParamValues(JSONObject.toJSONString(param), 0, 1000) .constructMapInvokeData().flush(response); } /** * Map 導出 * * @param response */ private void useWay3(HttpServletResponse response) { new ExcelDirector() .addFieldTitleTable(titleMap) .setData(excelExportMaps) .setExcelFileName("c201900202") //可不指定columnTypeMap ,默認爲String // .setColumnTypeMap(columnTypeMap) .constructMap() .flush(response); } /** * model 1 * * @param response */ private void useWay1(HttpServletResponse response) { //實體對象導出示例 new ExcelDirector() .addFieldTitleTable(titleMap) .setData(excelExportModels) .setExcelFileName("測試Excel.xlsx") .setTitles(new String[]{"北京統計", "20191229"}) .constructModel() .flush(response); } }
-
使用建造者模式設計Excel 導出
- UML類圖結構:
- 源碼:
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mfz.study</groupId>
<artifactId>excelbuild</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>excelbuild</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 相關代碼如下:
package com.mfz.study.util.build;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.util.Map;
/**
* @Description: 設置樣式庫-抽象
* @Author mengfanzhu
* @Date 2019/12/18 11:00
* @Version 1.0
*/
public abstract class AbstractBuildStyle {
/**
* 任何實現類,如果定義的是內容樣式,存入map的key必須是這個,否則不生效
*/
public static final String CONTENT_STYLE_KEY = "contentStyleKey";
/**
* 任何實現類,如果定義的是標題樣式且使用默認的設置Title方式,
* 則存入map的key必須是這個,否則不生效,如果同時自定義了設置Title的實現,則不受這個限制
*/
public static final String TITLE_STYLE_KEY = "titleStyleKey";
/**
* 任何實現類,如果定義的是表頭樣式,存入map的key必須是這個,否則不生效
*/
public static final String HEAD_STYLE_KEY = "headStyleKey";
/**
* @param workbook 表格
* @param styleMap 樣式庫
*/
abstract void setStyle(SXSSFWorkbook workbook, Map<String,CellStyle> styleMap);
}
package com.mfz.study.util.build;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import java.util.Map;
/**
* @Description: 設置標題抽象
* @Author mengfanzhu
* @Date 2019/12/18 10:55
* @Version 1.0
*/
@Getter
@Setter
public abstract class AbstractBuildTitle {
/**
* @param sheet 表格
* @param titles 標題數組
* @param heads 表頭列數組
* @param styleMap 樣式庫
* @param fieldTitleTable 列對應表頭名
*/
abstract void setTitle(SXSSFSheet sheet, String[] titles, Map<String, CellStyle> styleMap, String[] heads, Map<String, String> fieldTitleTable);
}
package com.mfz.study.util.build;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: 設置內容-抽象
* @Author mengfanzhu
* @Date 2019/12/18 11:11
* @Version 1.0
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class AbstractBuildContent {
Map<String, CellStyle> styleMap;
SXSSFSheet sheet;
AbstractGetFieldValue abstractGetFieldValue;
/**
* @param data
* @param columns
* @param columnTypes
*/
abstract void setContent(Collection data, String[] columns, LinkedHashMap<String, Integer> columnTypes);
}
package com.mfz.study.util.build;
/**
* @Description: 獲得屬性值抽象
* @Author mengfanzhu
* @Date 2019/12/18 10:35
* @Version 1.0
*/
public abstract class AbstractGetFieldValue {
/**
* 獲取屬性值方法
* @param obj 反射對象
* @param fieldName 屬性名
* @return
*/
abstract Object getFieldValue(Object obj,String fieldName);
}
package com.mfz.study.util.build;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: 建造者模式-抽象建造者
* @Author mengfanzhu
* @Date 2019/12/18 11:24
* @Version 1.0
*/
@Slf4j
public abstract class AbstractExcelBuilder {
/**
* 數據主體開始行下標
*/
int rowStartIndex = 0;
/**
* excel名稱
*/
String excelFileName;
/**
* 工作對象
*/
SXSSFWorkbook workbook;
/**
* 表格
*/
SXSSFSheet sheet;
/**
* 樣式庫
*/
Map<String, CellStyle> styleMap = new HashMap<>();
/**
* 屬性-標題對照
*/
LinkedHashMap<String, String> fieldTitleTable = new LinkedHashMap<>();
/**
* 獲取屬性值
*/
AbstractGetFieldValue abstractGetFieldValue;
/**
* 設置樣式
*/
AbstractBuildStyle abstractBuildStyle;
/**
* 設置標題+表頭
*/
AbstractBuildTitle abstractBuildTitle;
/**
* 設置內容
*/
AbstractBuildContent content;
/**
* 數據源所在類,需要實現接口
*
* @see BaseSetContentInterface
*/
Class invokeClass;
/**
* 數據源所在方法,入參
*
* @see BaseSetContentInterface
*/
String methodParamJson;
Integer methodParamLimitStart;
Integer methodParamLimitSize;
void addFieldTitleTable(String fieldName, String titleName) {
fieldTitleTable.put(fieldName, titleName);
}
/**
* 建造工作對象
*
* @param wb
* @return
*/
abstract AbstractExcelBuilder buildWorkbook(SXSSFWorkbook wb);
/**
* 建造表格
*
* @param sheetName
* @return
*/
abstract AbstractExcelBuilder buildSheet(String sheetName);
/**
* @param data 數據源
* @param heads 表頭
* @param columnTypeMap 列對應數據類型
* @param abstractSetContent
* @return
* @see AbstractBuildContent
*/
abstract AbstractExcelBuilder buildContent(Collection data, String[] heads, LinkedHashMap<String, Integer> columnTypeMap, Class<? extends AbstractBuildContent> abstractSetContent);
/**
* 構建標題+表頭
*
* @param titles
* @param heads
* @return
*/
abstract AbstractExcelBuilder buildTitlesAndHeads(String[] titles, String[] heads);
/**
* 文件名稱
*
* @param excelFileName
* @return
*/
abstract AbstractExcelBuilder buildExcelFileName(String excelFileName);
abstract AbstractExcelBuilder buildStyle();
/**
* @param clazz
* @return
* @see BaseSetContentInterface
*/
abstract AbstractExcelBuilder buildInvokeClass(Class clazz);
/**
* @param methodParamJson
* @param methodParamLimitStart
* @param methodParamLimitSize
* @return
* @see BaseSetContentInterface
*/
abstract AbstractExcelBuilder buildInvokeMethodParamValue(String methodParamJson, Integer methodParamLimitStart, Integer methodParamLimitSize);
/**
* 輸出流
*
* @param response
*/
abstract void flush(HttpServletResponse response);
abstract void flush(HttpServletResponse response, String excelFileName);
AbstractExcelBuilder buildColumn(int columnSize, boolean isAutoColumnWidth) {
if (columnSize == 0) {
return this;
}
if (isAutoColumnWidth) {
for (int i = 0; i < columnSize; i++) {
sheet.autoSizeColumn(i);
}
return this;
}
for (int i = 0; i < columnSize; i++) {
sheet.setDefaultColumnWidth(i);
}
return this;
}
/**
* 獲取屬性值的方式
*
* @param getFieldValue
*/
void getFieldValueImpl(AbstractGetFieldValue getFieldValue) {
if (null == getFieldValue) {
this.abstractGetFieldValue = new DefaultGetValue();
} else {
this.abstractGetFieldValue = getFieldValue;
}
}
void setStyleImpl(AbstractBuildStyle setStyle) {
if (null == setStyle) {
this.abstractBuildStyle = new DefaultBuildStyle();
} else {
this.abstractBuildStyle = setStyle;
}
}
void setTitleImpl(AbstractBuildTitle setTitle) {
if (null == setTitle) {
this.abstractBuildTitle = new DefaultBuildTitle();
} else {
this.abstractBuildTitle = setTitle;
}
}
void setContentImpl(Class<? extends AbstractBuildContent> abstractSetContent) {
try {
if (null == abstractSetContent) {
this.content = new DefaultBuildContentModel(styleMap, sheet, abstractGetFieldValue).initParams(rowStartIndex);
return;
}
AbstractBuildContent content = abstractSetContent.newInstance();
if (content instanceof DefaultBuildContentInvokeData) {
this.content = new DefaultBuildContentInvokeData(styleMap, sheet, abstractGetFieldValue)
.initParams(invokeClass, methodParamJson, methodParamLimitStart, methodParamLimitSize, rowStartIndex);
} else if (content instanceof DefaultBuildContentMap) {
this.content = new DefaultBuildContentMap(styleMap, sheet, abstractGetFieldValue).initParams(rowStartIndex);
} else if (content instanceof DefaultBuildContentModel) {
this.content = new DefaultBuildContentModel(styleMap, sheet, abstractGetFieldValue).initParams(rowStartIndex);
} else {
throw new IllegalArgumentException("Invalid AbstractBuildContent class: " + abstractSetContent.getName());
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public SXSSFWorkbook getWorkbook() {
return workbook;
}
}
package com.mfz.study.util.build;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import java.awt.Color;
import java.util.Map;
/**
* @Description: 設置樣式-默認
* @Author mengfanzhu
* @Date 2019/12/18 11:05
* @Version 1.0
*/
public class DefaultBuildStyle extends AbstractBuildStyle {
private static Color blue = new Color(135, 206, 255);
@Override
void setStyle(SXSSFWorkbook workbook, Map<String, CellStyle> styleMap) {
XSSFCellStyle style1 = (XSSFCellStyle) workbook.createCellStyle();
/**
* 標題樣式
*/
// 前景色(藍色)
XSSFColor myColor0= new XSSFColor(blue);
style1.setFillForegroundColor(myColor0);
// 設置單元格填充樣式
style1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 設置單元格邊框
style1.setBorderBottom(BorderStyle.THIN);
style1.setBorderLeft(BorderStyle.THIN);
style1.setBorderRight(BorderStyle.THIN);
style1.setBorderTop(BorderStyle.THIN);
style1.setAlignment(HorizontalAlignment.CENTER);
// 生成一個字體
XSSFFont font0 = (XSSFFont) workbook.createFont();
font0.setFontHeightInPoints((short) 14);
font0.setBold(true);
// 把字體應用到當前的樣式
style1.setFont(font0);
styleMap.put(TITLE_STYLE_KEY, style1);
/**
* 表頭樣式
*/
// 生成一個字體
XSSFFont font = (XSSFFont) workbook.createFont();
font.setFontHeightInPoints((short) 12);
font.setBold(true);
// 把字體應用到當前的樣式
style1.setFont(font);
styleMap.put(HEAD_STYLE_KEY, style1);
/**
* 內容樣式
*/
XSSFCellStyle style2 = (XSSFCellStyle) workbook.createCellStyle();
XSSFColor white = new XSSFColor(Color.WHITE);
style2.setFillBackgroundColor(white);
style2.setBorderBottom(BorderStyle.THIN);
style2.setBorderLeft(BorderStyle.THIN);
style2.setBorderRight(BorderStyle.THIN);
style2.setBorderTop(BorderStyle.THIN);
style2.setAlignment(HorizontalAlignment.CENTER);
style2.setVerticalAlignment(VerticalAlignment.CENTER);
// 生成另一個字體
XSSFFont font2 = (XSSFFont) workbook.createFont();
font2.setBold(false);
// 把字體應用到當前的樣式
style2.setFont(font2);
styleMap.put(CONTENT_STYLE_KEY, style2);
}
}
package com.mfz.study.util.build;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.util.Assert;
import java.util.Map;
/**
* @Description: 默認-設置標題-表頭
* @Author mengfanzhu
* @Date 2019/12/18 10:57
* @Version 1.0
*/
public class DefaultBuildTitle extends AbstractBuildTitle {
@Override
void setTitle(SXSSFSheet sheet, String[] titles, Map<String, CellStyle> styleMap, String[] heads, Map<String, String> fieldTitleTable) {
int rowIndex = 0;
if (null != titles && titles.length != 0) {
SXSSFRow rowTitle = sheet.createRow(rowIndex);
for (int i = 0, j = titles.length; i < j; i++) {
SXSSFCell cell = rowTitle.createCell(i);
cell.setCellType(CellType.STRING);
cell.setCellStyle(styleMap.get(AbstractBuildStyle.TITLE_STYLE_KEY));
cell.setCellValue(titles[i]);
}
rowIndex++;
}
if (null != heads && heads.length != 0) {
Assert.notEmpty(fieldTitleTable, "fieldTitleTable不能爲空!");
SXSSFRow rowHead = sheet.createRow(rowIndex);
for (int i = 0, j = heads.length; i < j; i++) {
SXSSFCell cell = rowHead.createCell(i);
cell.setCellType(CellType.STRING);
cell.setCellStyle(styleMap.get(AbstractBuildStyle.HEAD_STYLE_KEY));
XSSFRichTextString textString = new XSSFRichTextString(fieldTitleTable.get(heads[i]));
cell.setCellValue(textString);
}
}
}
}
package com.mfz.study.util.build;
/**
* @Description: 默認獲取屬性值
* @Author mengfanzhu
* @Date 2019/12/18 10:36
* @Version 1.0
*/
public class DefaultGetValue extends AbstractGetFieldValue{
@Override
Object getFieldValue(Object obj, String fieldName) {
if(null == fieldName){
return "";
}
return ReflectingHelper.getValueByField(obj,fieldName.toString());
}
}
package com.mfz.study.util.build;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
/**
* @Description: 反射輔助類
* @Author mengfanzhu
* @Date 2019/12/18 10:38
* @Version 1.0
*/
@Slf4j
public abstract class ReflectingHelper {
public static Object getValueByField(Object obj, String fieldName) {
if (obj == null || StringUtils.isEmpty(fieldName)) {
return null;
}
Field field = getFieldByFieldName(obj, fieldName);
Object val = null;
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
val = field.get(obj);
return val;
} catch (IllegalAccessException e) {
e.printStackTrace();
log.error("反射輔助類-獲取值異常 " + e.toString());
} finally {
field.setAccessible(false);
}
return null;
}
/**
* @param obj
* @param fieldName
* @return
*/
public static Field getFieldByFieldName(Object obj, String fieldName) {
for (Class<?> superClass = obj.getClass(); superClass != T.class; superClass = superClass.getSuperclass()) {
try {
return superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
e.printStackTrace();
log.error("反射輔助類-getFieldByFieldName-Exception:" + e.toString());
}
}
return null;
}
}
package com.mfz.study.util.build;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: 內容設置-默認-處理Model 類型數據源
* @Author mengfanzhu
* @Date 2019/12/18 11:14
* @Version 1.0
*/
@Slf4j
@NoArgsConstructor
public class DefaultBuildContentModel extends AbstractBuildContent {
private int rowStartIndex = 0;
public DefaultBuildContentModel(Map<String, CellStyle> styleMap, SXSSFSheet sheet, AbstractGetFieldValue abstractGetFieldValue) {
super(styleMap, sheet, abstractGetFieldValue);
}
public DefaultBuildContentModel initParams(int rowStartIndex) {
this.rowStartIndex = rowStartIndex;
return this;
}
@Override
void setContent(Collection data, String[] columns, LinkedHashMap<String, Integer> columnTypes) {
modelHandler(data, columns);
}
private void modelHandler(Collection data, String[] columns) {
Assert.notEmpty(columns, "columns cannot be null");
if (CollectionUtils.isEmpty(data)) {
return;
}
log.info("modelHandler start");
SXSSFRow row = null;
SXSSFCell cell = null;
Object fieldValue = null;
XSSFRichTextString xssfVal = null;
Iterator iter = data.iterator();
while (iter.hasNext()) {
rowStartIndex++;
row = sheet.createRow(rowStartIndex);
Object t = iter.next();
for (int i = 0, j = columns.length; i < j; i++) {
cell = row.createCell(i);
cell.setCellType(CellType.STRING);
cell.setCellStyle(styleMap.get(AbstractBuildStyle.CONTENT_STYLE_KEY));
fieldValue = abstractGetFieldValue.getFieldValue(t, columns[i]);
xssfVal = new XSSFRichTextString(String.valueOf(fieldValue));
cell.setCellValue(xssfVal);
}
}
}
}
package com.mfz.study.util.build;
public enum ExcelCellType {
STRING(1),
DECIMAL(2),
_NONE(3),
TIME(4),
DATE(5);
private final int code;
ExcelCellType(int code) {
this.code = code;
}
public static ExcelCellType getByCode(int code) {
for (ExcelCellType type : values()) {
if (type.code == code) {
return type;
}
}
throw new IllegalArgumentException("Invalid ExcelCellType code: " + code);
}
public int getCode() {
return code;
}
}
package com.mfz.study.util.build;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @Description: 內容設置-默認-處理Map 類型數據源
* @Author mengfanzhu
* @Date 2019/12/18 11:14
* @Version 1.0
*/
@Slf4j
@NoArgsConstructor
public class DefaultBuildContentMap extends AbstractBuildContent {
private int rowStartIndex = 0;
public DefaultBuildContentMap(Map<String, CellStyle> styleMap, SXSSFSheet sheet, AbstractGetFieldValue abstractGetFieldValue) {
super(styleMap, sheet, abstractGetFieldValue);
}
public DefaultBuildContentMap initParams(int rowStartIndex){
this.rowStartIndex = rowStartIndex;
return this;
}
@Override
void setContent(Collection data, String[] columns, LinkedHashMap<String, Integer> columnTypes) {
mapHandler(data, columnTypes);
}
public void mapHandler(Collection data, LinkedHashMap<String, Integer> columnTypes) {
Assert.notEmpty(columnTypes, "columnTypes cannot be null");
if (CollectionUtils.isEmpty(data)) {
return;
}
log.info("mapHandler start");
SXSSFRow row = null;
SXSSFCell cell = null;
Iterator iter = data.iterator();
int cellIndex = 0;
Object fieldValue = null;
Integer fieldType = null;
//row loop
while (iter.hasNext()) {
Object t = iter.next();
if (!(t instanceof Map)) {
throw new IllegalArgumentException("DefaultBuildContentMap->data type must is List<Map<String,Object>> !");
}
rowStartIndex++;
cellIndex = 0;
row = sheet.createRow(rowStartIndex);
Map<String, Object> map = (Map<String, Object>) t;
//cell loop
for (String fieldName : columnTypes.keySet()) {
fieldValue = map.get(fieldName);
if(null == fieldValue){
cellIndex++;
continue;
}
cell = row.createCell(cellIndex);
cell.setCellType(CellType.STRING);
cell.setCellStyle(styleMap.get(AbstractBuildStyle.CONTENT_STYLE_KEY));
fieldType = columnTypes.get(fieldName);
switch (ExcelCellType.getByCode(fieldType)) {
case STRING:
if (fieldValue instanceof String) {
String cellValue = String.valueOf(fieldValue);
cell.setCellValue(cellValue);
}
break;
case DECIMAL:
if (fieldValue instanceof BigDecimal) {
BigDecimal cellValue = (BigDecimal) fieldValue;
cellValue.setScale(2, BigDecimal.ROUND_DOWN);
cell.setCellValue(cellValue.toString());
}
break;
case _NONE:
break;
case TIME:
Timestamp resultTime = (Timestamp) fieldValue;
cell.setCellValue(getSimpleStringDate(resultTime));
break;
case DATE:
Date resultDate = (Date) fieldValue;
cell.setCellValue(getSimpleStringDate(resultDate));
break;
}
cellIndex++;
}
}
}
public static String getSimpleStringDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
return sdf.format(date);
}
}
package com.mfz.study.util.build;
import com.mfz.study.util.SpringContextHolder;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.springframework.context.ApplicationContext;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: 內容設置-默認-處理Model 類型數據源
* @Author mengfanzhu
* @Date 2019/12/18 11:14
* @Version 1.0
*/
@Slf4j
@NoArgsConstructor
public class DefaultBuildContentInvokeData extends AbstractBuildContent {
private int rowStartIndex = 0;
private Class invokeClass;
/**
* @see BaseSetContentInterface
*/
private String json;
private Integer limitStart;
private Integer limitSize;
public DefaultBuildContentInvokeData(Map<String, CellStyle> styleMap, SXSSFSheet sheet, AbstractGetFieldValue abstractGetFieldValue) {
super(styleMap, sheet, abstractGetFieldValue);
}
public DefaultBuildContentInvokeData initParams(Class invokeClass, String methodParamJson, Integer methodParamLimitStart, Integer methodParamLimitSize, int rowStartIndex) {
this.rowStartIndex = rowStartIndex;
this.invokeClass = invokeClass;
this.json = methodParamJson;
this.limitStart = methodParamLimitStart;
this.limitSize = methodParamLimitSize;
return this;
}
@Override
void setContent(Collection data, String[] columns, LinkedHashMap<String, Integer> columnTypes) {
try {
if (!(invokeClass.newInstance() instanceof BaseSetContentInterface)) {
throw new Exception("DefaultBuildContentInvokeData.invokeClass need to implement BaseSetContentInterface !");
}
log.info("DefaultBuildContentInvokeData setContent start,invokeClasss is {},json is {},limitStart is {},limitSize is {} .", invokeClass, json, limitStart, limitSize);
Method invokeMethod = BaseSetContentInterface.class.getMethods()[0];
ApplicationContext context = SpringContextHolder.getApplicationContext();
Object bean = context.getBean(invokeClass);
/**
* 給實例化的類注入需要的bean (@Autowired) ,如果不注入,被@Autowired註解的變量會報空指針
*/
context.getAutowireCapableBeanFactory().autowireBean(bean);
data = (Collection) invokeMethod.invoke(bean, json, limitStart, limitSize);
DefaultBuildContentMap defaultSetContentMap = new DefaultBuildContentMap(styleMap, sheet, abstractGetFieldValue).initParams(rowStartIndex);
for (; data.size() > 0;
limitStart += limitSize, data = (Collection) invokeMethod.invoke(bean, json, limitStart, limitSize)) {
defaultSetContentMap.mapHandler(data, columnTypes);
}
} catch (Exception e) {
log.error("DefaultBuildContentInvokeData exception", e.getMessage());
}
}
}
package com.mfz.study.util.build;
import java.util.List;
import java.util.Map;
/**
* @Description: invokeData 需要實現此接口
* @Author mengfanzhu
* @Date 2019/12/20 10:11
* @Version 1.0
*/
public interface BaseSetContentInterface {
/**
* @param json 數據構造入參
* @param limitStart 數據分頁開始
* @param size 每頁數據量
* @return
* @see DefaultBuildContentInvokeData
*/
List<? extends Map<String, Object>> getExcelExportData(String json, Integer limitStart, Integer size);
}
package com.mfz.study.util.build;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Collection;
import java.util.LinkedHashMap;
/**
* @Description: 建造者模式-具體建造者
* @Author mengfanzhu
* @Date 2019/12/18 11:39
* @Version 1.0
*/
@Slf4j
public class ExcelBuilder extends AbstractExcelBuilder {
/**
* 構建滑動窗口數
* new SXSSFWorkbook() -> 默認爲100
*/
private static final Integer ROW_ACCESS_WINDOWSIZE = 200;
@Override
AbstractExcelBuilder buildWorkbook(SXSSFWorkbook wb) {
if (null == workbook) {
workbook = new SXSSFWorkbook(ROW_ACCESS_WINDOWSIZE);
} else {
workbook = wb;
}
return this;
}
@Override
AbstractExcelBuilder buildSheet(String sheetName) {
if (StringUtils.isEmpty(sheetName)) {
sheetName = "sheet1";
}
sheet = workbook.createSheet(sheetName);
return this;
}
@Override
AbstractExcelBuilder buildContent(Collection data, String[] heads, LinkedHashMap<String, Integer> columnTypeMap, Class<? extends AbstractBuildContent> abstractSetContent) {
setContentImpl(abstractSetContent);
this.content.setContent(data, heads, columnTypeMap);
return this;
}
@Override
AbstractExcelBuilder buildTitlesAndHeads(String[] titles, String[] heads) {
Assert.notEmpty(heads, "buildTitlesAndHeads->heads cannot be null");
if (null != titles && titles.length > 0) {
super.rowStartIndex = 1;
}
abstractBuildTitle.setTitle(sheet, titles, styleMap, heads, fieldTitleTable);
return this;
}
@Override
AbstractExcelBuilder buildExcelFileName(String excelFileName) {
Assert.notNull(excelFileName, "excelFileName cannot be null");
return this;
}
@Override
AbstractExcelBuilder buildStyle() {
abstractBuildStyle.setStyle(workbook, styleMap);
return this;
}
@Override
AbstractExcelBuilder buildInvokeClass(Class clazz) {
super.invokeClass = clazz;
return this;
}
@Override
AbstractExcelBuilder buildInvokeMethodParamValue(String methodParamJson, Integer methodParamLimitStart, Integer methodParamLimitSize) {
super.methodParamJson = methodParamJson;
super.methodParamLimitStart = methodParamLimitStart;
super.methodParamLimitSize = methodParamLimitSize;
return this;
}
/**
* 設定每次從內存讀多少
*/
private static byte[] buff = new byte[2048];
@Override
void flush(HttpServletResponse response) {
flush(response, this.excelFileName);
}
@Override
void flush(HttpServletResponse response, String excelFileName) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
workbook.write(os);
} catch (Exception e) {
e.printStackTrace();
log.error(e.toString());
}
//設置response參數,可以打開下載頁面
packResponse(response, excelFileName);
try (
// 封裝爲緩衝,減少讀寫次數,提高效率
BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(os.toByteArray()));
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
) {
//循環從內存讀取,並寫到Resonse響應流中
int bytesRead;
while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
bos.write(buff, 0, bytesRead);
}
} catch (IOException e) {
log.error(e.toString());
} finally {
// 將此workbook對應的臨時文件刪除
if (null != workbook) {
workbook.dispose();
}
}
}
private void packResponse(HttpServletResponse response, String excelFileName) {
try {
if(StringUtils.isEmpty(excelFileName)){
excelFileName = "excel-"+System.currentTimeMillis()+".xlsx";
}
response.reset();
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(excelFileName.getBytes(), "iso-8859-1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
package com.mfz.study.util.build;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Set;
/**
* @Description: 建造者模式-負責規範流程
* @Author mengfanzhu
* @Date 2019/12/18 13:59
* @Version 1.0
*/
public class ExcelDirector {
private AbstractExcelBuilder abstractExcelBuilder = new ExcelBuilder();
private String sheetName;
private String excelFileName;
private String[] heads;
private String[] titles;
private LinkedHashMap<String, Integer> columnTypeMap;
private Collection data;
private SXSSFWorkbook workbook;
private boolean isAutoColumnWidth = false;
private Integer columnSize = null;
/**
* 數據源所在類,方法入參
* implements
*
* @see BaseSetContentInterface
*/
private Class clazz;
private String methodParamJson;
private Integer methodParamLimitStart;
private Integer methodParamLimitSize;
public ExcelDirector() {
overrideDefaultGetFieldValue(null)
.overrideDefaultSetTitle(null)
.overrideDefaultSetStyle(null);
}
/**
* 構建一個對象的流程
*
* @return
* @see TestController
*/
public ExcelDirector constructModel() {
Assert.notNull(heads, "constructModel -> heads need to do ");
Assert.notNull(data, "constructModel -> data need to do ");
abstractExcelBuilder
.buildWorkbook(workbook)
.buildSheet(sheetName)
.buildColumn(columnSize, isAutoColumnWidth)
.buildStyle()
.buildTitlesAndHeads(titles, heads)
.buildExcelFileName(excelFileName)
.buildContent(data, heads, null, DefaultBuildContentModel.class);
return this;
}
/**
* //指定列對應類型
* LinkedHashMap<String, Integer> columnTypeMap = new LinkedHashMap<>();
* columnTypeMap.put("id", 1); //1 -> String
* columnTypeMap.put("name", 1);
* columnTypeMap.put("address", 1);
* //指定表頭
* LinkedHashMap<String, String> titleMap = new LinkedHashMap<>();
* titleMap.put("id", "唯一標識符");
* titleMap.put("name", "名字");
* titleMap.put("address", "地址");
* <p>
* new ExcelDirector()
* .addFieldTitleTable(titles)
* //可不指定columnTypeMap 默認爲String
* .setColumnTypeMap(columnTypeMap)
* .setData(result)
* .setExcelFileName(filename)
* .constructMap()
* .flush(response);
* <p>
* 構建一個Map數據源的流程
*
* @return
* @see TestController
*/
public ExcelDirector constructMap() {
if (columnTypeMap == null || columnTypeMap.size() != columnSize) {
columnTypeMap = new LinkedHashMap<>();
for (String fieldName : heads) {
columnTypeMap.put(fieldName, CellType.STRING.getCode());
}
}
Assert.notNull(heads, "constructMap -> heads need to do ");
Assert.notNull(columnTypeMap, "constructMap -> columnTypeMap need to do ");
Assert.notNull(data, "constructMap -> data need to do ");
abstractExcelBuilder
.buildWorkbook(workbook)
.buildSheet(sheetName)
.buildColumn(columnSize, isAutoColumnWidth)
.buildStyle()
.buildTitlesAndHeads(titles, heads)
.buildExcelFileName(excelFileName)
.buildContent(data, null, columnTypeMap, DefaultBuildContentMap.class);
return this;
}
/**
* @return
* @see TestController
* 構建一個Map 類型,反射數據源的流程
* 示例:
* 1.
* //指定列對應類型
* LinkedHashMap<String, Integer> columnTypeMap = new LinkedHashMap<>();
* columnTypeMap.put("id", 1); //1 -> String
* columnTypeMap.put("name", 1);
* columnTypeMap.put("address", 1);
* //指定表頭
* LinkedHashMap<String, String> titleMap = new LinkedHashMap<>();
* titleMap.put("id", "唯一標識符");
* titleMap.put("name", "名字");
* titleMap.put("address", "地址");
* <p>
* SXSSFWorkbook wb = new ExcelDirector()
* .addFieldTitleTable(titleMap)
* .setColumnTypeMap(columnTypeMap)
* .setBoundInvokeClass(this.getClass())
* .setBoundInvokeMethodParamValues(JSONObject.toJSONString(params), 0, 5000)
* .constructMapInvokeData()
* .getWorkbook();
* <p>
* 2. 直接導出
* new ExcelDirector()
* .addFieldTitleTable(titleMap)
* .setTitles("sheet1")
* .setColumnTypeMap(columnTypeMap)
* .setBoundInvokeClass(this.getClass())
* .setBoundInvokeMethodParamValues(JSONObject.toJSONString(params),0,1000)
* .constructMapInvokeData()
* .flush(response,"fileName");
*/
public ExcelDirector constructMapInvokeData() {
Assert.notNull(clazz, "buildInvokeClass(clazz) need to do ");
Assert.notNull(methodParamJson, "buildInvokeMethodParamValue(methodParamJson , methodParamLimitStart,methodParamLimitSize) need to do");
Assert.notNull(methodParamLimitStart, "buildInvokeMethodParamValue(methodParamJson , methodParamLimitStart,methodParamLimitSize) need to do");
Assert.notNull(methodParamLimitSize, "buildInvokeMethodParamValue(methodParamJson , methodParamLimitStart,methodParamLimitSize) need to do");
abstractExcelBuilder
.buildWorkbook(workbook)
.buildSheet(sheetName)
.buildColumn(columnSize, isAutoColumnWidth)
.buildStyle()
.buildTitlesAndHeads(titles, heads)
.buildInvokeClass(clazz)
.buildInvokeMethodParamValue(methodParamJson, methodParamLimitStart, methodParamLimitSize)
.buildContent(null, null, columnTypeMap, DefaultBuildContentInvokeData.class);
return this;
}
/**
* 添加屬性標題對照
*
* @param fieldTitle
* @return
*/
public ExcelDirector addFieldTitleTable(LinkedHashMap<String, String> fieldTitle) {
if (fieldTitle == null || fieldTitle.size() == 0) {
return null;
}
Set<String> strings = fieldTitle.keySet();
for (String key : strings) {
abstractExcelBuilder.addFieldTitleTable(key, fieldTitle.get(key));
}
setHeads(strings.toArray(new String[strings.size()]));
return this;
}
public ExcelDirector addFieldTitleTable(String fieldName, String titleName) {
abstractExcelBuilder.addFieldTitleTable(fieldName, titleName);
return this;
}
public SXSSFWorkbook getWorkbook() {
return abstractExcelBuilder.getWorkbook();
}
public ExcelDirector setWorkbook(SXSSFWorkbook workbook) {
abstractExcelBuilder.workbook = workbook;
return this;
}
/**
* 覆蓋-獲取屬性值
*
* @param getFieldValue
* @return
*/
public ExcelDirector overrideDefaultGetFieldValue(AbstractGetFieldValue getFieldValue) {
abstractExcelBuilder.getFieldValueImpl(getFieldValue);
return this;
}
public ExcelDirector isAutoColumnWidth(boolean isAutoColumnWidth) {
this.isAutoColumnWidth = isAutoColumnWidth;
return this;
}
/**
* 覆蓋-默認設置的樣式
*
* @param setStyle
* @return
*/
public ExcelDirector overrideDefaultSetStyle(AbstractBuildStyle setStyle) {
abstractExcelBuilder.setStyleImpl(setStyle);
return this;
}
/**
* 覆蓋-默認設置標題
*
* @param setTitle
* @return
*/
public ExcelDirector overrideDefaultSetTitle(AbstractBuildTitle setTitle) {
abstractExcelBuilder.setTitleImpl(setTitle);
return this;
}
/**
* 寫到響應流
*
* @param response
*/
public void flush(HttpServletResponse response) {
abstractExcelBuilder.flush(response);
}
public void flush(HttpServletResponse response, String excelFileName) {
abstractExcelBuilder.flush(response, excelFileName);
}
public ExcelDirector setSheetName(String sheetName) {
this.sheetName = sheetName;
return this;
}
public ExcelDirector setHeads(String[] heads) {
if (columnSize == null) {
columnSize = heads.length;
}
this.heads = heads;
return this;
}
public Collection getData() {
return data;
}
public ExcelDirector setData(Collection data) {
this.data = data;
return this;
}
/**
* 設置列對應數據類型
*
* @param columnTypeMap
* @return
*/
public ExcelDirector setColumnTypeMap(LinkedHashMap<String, Integer> columnTypeMap) {
this.columnTypeMap = columnTypeMap;
return this;
}
/**
* 設置標題
*
* @param titles
* @return
*/
public ExcelDirector setTitles(String... titles) {
this.titles = titles;
return this;
}
/**
* 設置文件名稱
*
* @param excelFileName
* @return
*/
public ExcelDirector setExcelFileName(String excelFileName) {
this.excelFileName = excelFileName;
return this;
}
/**
* 設置反射類名
*
* @param invokeClass
* @return
*/
public ExcelDirector setBoundInvokeClass(Class invokeClass) {
this.clazz = invokeClass;
return this;
}
/**
* 設置反射方法中參數值
*
* @param methodParamJson
* @param methodParamLimitStart
* @param methodParamLimitSize
* @return
* @see BaseSetContentInterface
*/
public ExcelDirector setBoundInvokeMethodParamValues(String methodParamJson, Integer methodParamLimitStart, Integer methodParamLimitSize) {
this.methodParamJson = methodParamJson;
this.methodParamLimitStart = methodParamLimitStart;
this.methodParamLimitSize = methodParamLimitSize;
return this;
}
}
- 使用示例
package com.mfz.study.util;
import com.alibaba.fastjson.JSONObject;
import com.mfz.study.util.build.BaseSetContentInterface;
import com.mfz.study.util.build.TestController;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description: 反射數據源實現
* @Author mengfanzhu
* @Date 2019/12/18 14:19
* @Version 1.0
*/
@Service
public class InvokeDataSource implements BaseSetContentInterface {
@Override
public List<? extends Map<String, Object>> getExcelExportData(String json, Integer limitStart, Integer size) {
TestController.ExcelExportModel param = JSONObject.parseObject(json,new TestController.ExcelExportModel().getClass());
System.out.println(param.getAddress());
System.out.println(param.getId());
System.out.println(param.getName());
//TODO do something ... 根據入參 獲取數據源
if(limitStart >10){
return new ArrayList<>();
}
List<HashMap<String, Object>> data = new ArrayList<>();
for (int i = 0; i < size; i++) {
HashMap<String, Object> d = new HashMap<>();
d.put("name", "黎明" + i);
d.put("address", "北京朝陽" + i);
d.put("id", "00001" + i);
data.add(d);
}
return data;
}
}