import lombok.Builder;
import lombok.Data;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @description: java–poi生成excel动态合并内容相同的行+水平垂直居中+大标题 浏览器直接下载
* @author: Dai Yuanchuan
* @create: 2019-03-24 14:38
**/
public class ExcelUtil {
/**
* 工具类主入口
*
* @param title excel 头
* @param maps Map>>
* @param mergeIndex 需要合并的列的下标组成的int数组
* @param fileName 文件名
* @param response 响应头
* @return
*/
public static void createExcel(String[] title, Map>> maps,
int[] mergeIndex, String fileName, HttpServletResponse response) {
// 初始化excel模板
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = null;
int n = 0;
/**
* 循环sheet页
* List>>
*/
for (Map.Entry>> entry : maps.entrySet()) {
// 实例化sheet对象并且设置sheet名称,book对象
try {
sheet = workbook.createSheet();
workbook.setSelectedTab(0);
} catch (Exception e) {
e.printStackTrace();
}
// 初始化头部
HSSFCellStyle cellStyle1 = initExcelHead(sheet, workbook, n, entry, fileName, title);
// 得到当前sheet下的数据集合 List>
List> list = entry.getValue();
// 遍历该数据集合
List poiModels = new ArrayList<>();
if (null != workbook) {
Iterator iterator = list.iterator();
// 这里1是从excel的第三行开始,第一二行已经塞入标题了
int index = 2;
while (iterator.hasNext()) {
HSSFRow row = sheet.createRow(index);
// 设置单元格的高
row.setHeightInPoints(43);
// 取得当前这行的map,该map中以key,value的形式存着这一行值
Map map = (Map) iterator.next();
// 循环列数,给当前行塞值
currentRowAssignment(title, index, poiModels, mergeIndex, map, sheet, cellStyle1, row, list);
index++;
}
}
n++;
}
//直接从浏览器下载
setExportHTTPResponse(workbook, fileName, response);
}
/**
* 合并单元格
*
* @param poiModels
* @param i
* @param index
* @param sheet
* @param map
* @param title
*/
private static void merge(List poiModels, int i, int index,
HSSFSheet sheet, Map map, String[] title) {
/**
* 当前行的当前列与上一行的当前列的内容不一致时,则把当前行以上的合并
* 参数1: 从第二行开始,
* 参数2: 到第几行,
* 参数3: 从某一列开始,
* 参数4: 到第几列
*/
CellRangeAddress cra = new CellRangeAddress(poiModels.get(i).getRowIndex(), index - 1,
poiModels.get(i).getCellIndex(), poiModels.get(i).getCellIndex());
//在sheet里增加合并单元格
sheet.addMergedRegion(cra);
/*重新记录该列的内容为当前内容,行标记改为当前行标记*/
poiModels.get(i).setContent(map.get(title[i]).toString());
poiModels.get(i).setRowIndex(index);
poiModels.get(i).setCellIndex(i);
}
/**
* 设置导出的HTTP响应头
*
* @param workbook 最终需要导出的excel数据
* @param fileName 导出的文件名
* @param response 导出的响应头
*/
private static void setExportHTTPResponse(Workbook workbook, String fileName, HttpServletResponse response) {
if (workbook != null) {
ByteArrayOutputStream byteArrayOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
workbook.write(byteArrayOutputStream);
String suffix = ".xls";
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition",
"attachment;filename=" + new String((fileName + suffix).getBytes(), "iso-8859-1"));
OutputStream outputStream = response.getOutputStream();
outputStream.write(byteArrayOutputStream.toByteArray());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 设置excel 头部的字体
*
* @param workbook 需要设置的工作部实体
* @return 返回一个 HSSFFont
*/
private static HSSFFont setExcelHeaderFont(HSSFWorkbook workbook) {
// 初始化head,填值标题行(第一行) 设置字体
HSSFFont font = workbook.createFont();
//字体高度
font.setFontHeightInPoints((short) 20);
//字体颜色
font.setColor(HSSFFont.COLOR_NORMAL);
//字体
font.setFontName("黑体");
//宽度
font.setBold(true);
font.setFontHeightInPoints((short) 13);
//是否使用斜体
font.setItalic(false);
return font;
}
/**
* 设置单元格基本的 水平居中 自动换行 字体
*
* @param workbook 需要设置的工作簿实体
* @return 返回一个 HSSFCellStyle
*/
private static HSSFCellStyle setHSSFCellStyle(HSSFWorkbook workbook) {
// 设置单元格类型
HSSFCellStyle cellStyle0 = workbook.createCellStyle();
// 水平布局:居中
cellStyle0.setAlignment(HorizontalAlignment.CENTER);
// 自动换行
cellStyle0.setWrapText(true);
return cellStyle0;
}
/**
* 创建excel第一行
*
* @param sheet
* @param fileName 文件名
* @param cellStyle 样式
* @param title 头部
*/
private static void createExcelFirstLine(HSSFSheet sheet, String fileName, HSSFCellStyle cellStyle, String[] title) {
// 创建第一行
HSSFRow row0 = sheet.createRow(0);
HSSFCell cell0 = row0.createCell(0, CellType.STRING);
// 标题名称
cell0.setCellValue(fileName);
cell0.setCellStyle(cellStyle);
// 合并单元格(起始行,截止行,起始列,截止列)
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, title.length));
}
/**
* 创建excel头部
*
* @param sheet
* @param title 头部数组
* @param cellStyle 头部样式
*/
private static void createExcelHeader(HSSFSheet sheet, String[] title, HSSFCellStyle cellStyle) {
HSSFRow row1 = sheet.createRow(1);
for (int i = 0; i < title.length; i++) {
//设置单元格宽度
if (i == 0) {
sheet.setColumnWidth(i, "5897845697f84b3bf6acd1883be841ad".getBytes().length * 1 * 256);
} else if (i == 3) {
sheet.setColumnWidth(i, title[i].getBytes().length * 4 * 256);
} else {
sheet.setColumnWidth(i, title[i].getBytes().length * 2 * 256);
}
// 创建单元格,指定类型
HSSFCell cell1 = row1.createCell(i, CellType.STRING);
cell1.setCellValue(title[i]);
cell1.setCellStyle(cellStyle);
}
}
/**
* 初始化Excel头部信息 包含第一行 、 第二行
*
* @param sheet
* @param workbook 需要设置的工作簿实体
* @param n
* @param entry
* @param fileName 文件名
* @param title 头部
* @return 返回一个 HSSFCellStyle
*/
private static HSSFCellStyle initExcelHead(HSSFSheet sheet, HSSFWorkbook workbook, int n,
Map.Entry>> entry, String fileName, String[] title) {
// 初始化head,填值标题行(第一行) 设置字体
HSSFFont font = setExcelHeaderFont(workbook);
// 设置第一行 第二行 单元格的类型
HSSFCellStyle cellStyle0 = setHSSFCellStyle(workbook);
// 设置字体
cellStyle0.setFont(font);
// 创建第一行
createExcelFirstLine(sheet, fileName, cellStyle0, title);
// 垂直居中
cellStyle0.setVerticalAlignment(VerticalAlignment.CENTER);
// 创建excel表格头部
createExcelHeader(sheet, title, cellStyle0);
// 设置其他的 单元格类型
HSSFCellStyle cellStyle1 = setHSSFCellStyle(workbook);
// 垂直居中
cellStyle1.setVerticalAlignment(VerticalAlignment.CENTER);
return cellStyle1;
}
/**
* 循环列数 给当前行赋值
*
* @param title excel 头
* @param index 行数标识
* @param poiModels 当前行的数据集合
* @param mergeIndex 需要合并的列的下标组成的int数组
* @param map 取得当前这行的map,该map中以key,value的形式存着这一行值
* @param sheet
* @param cellStyle 单元格的样式
* @param row 创建的单元格的行的实体
* @param list 得到当前sheet下的数据集合 List>
*/
private static void currentRowAssignment(String[] title, int index, List poiModels, int[] mergeIndex,
Map map, HSSFSheet sheet, HSSFCellStyle cellStyle,
HSSFRow row, List> list) {
for (int i = 0; i < title.length; i++) {
String old = "";
// old存的是上一行统一位置的单元的值,第一行是最上一行了,所以从第二行开始记
if (index > 2) {
old = poiModels.get(i) == null ? "" : poiModels.get(i).getContent();
}
// 循环需要合并的列
for (int j = 0; j < mergeIndex.length; j++) {
if (index == 2) {
// 记录第三行的开始行和开始列
Object value = map.get(title[i]);
poiModels.add(PoiModel.builder()
.oldContent(value == null ? "" : value.toString())
.content(value == null ? "" : value.toString())
.rowIndex(2)
.cellIndex(i)
.build());
break;
// 这边i>0也是因为第一列已经是最前一列了,只能从第二列开始
} else if (i > 0 && mergeIndex[j] == i) {
/**
* 当前同一列的内容与上一行同一列不同时,把那以上的合并,
* 或者在当前元素一样的情况下,前一列的元素并不一样,这种情况也合并
* 如果不需要考虑当前行与上一行内容相同,但是它们的前一列内容不一样则不合并的情况,把下面条件中(rowSameContent && colSameContent)去掉就行
*/
// 当前行 的内容 与 上一行 的内容 是否相同
boolean rowSameContent = poiModels.get(i).getContent().equals(map.get(title[i]).toString());
// 当前列 的内容 与 前一列 的内容 是否不同
boolean colSameContent = !poiModels.get(i - 1).getOldContent().equals(map.get(title[i - 1]).toString());
// 最终的判断结果
boolean finalResult = !rowSameContent || (rowSameContent && colSameContent);
if (finalResult) {
merge(poiModels, i, index, sheet, map, title);
}
}
// 处理第一列的情况
if (mergeIndex[j] == i && i == 0 && !poiModels.get(i).getContent().equals(map.get(title[i]).toString())) {
// 当前行的当前列与上一行的当前列的内容不一致时,则把当前行以上的合并
merge(poiModels, i, index, sheet, map, title);
}
// 最后一行没有后续的行与之比较,所以当到最后一行时则直接合并对应列的相同内容
if (mergeIndex[j] == i && index == list.size() + 1) {
CellRangeAddress cra = new CellRangeAddress(poiModels.get(i).getRowIndex(),
index, poiModels.get(i).getCellIndex(), poiModels.get(i).getCellIndex());
//在sheet里增加合并单元格
sheet.addMergedRegion(cra);
}
}
HSSFCell cell = row.createCell(i, CellType.STRING);
Object value = map.get(title[i]);
cell.setCellValue(value == null ? "" : value.toString());
cell.setCellStyle(cellStyle);
// 在每一个单元格处理完成后,把这个单元格内容设置为old内容
poiModels.get(i).setOldContent(old);
}
}
public static void main(String args[]){
String[] headers = {
"id",
"产品名称",
"产品名称id",
"客户名称",
"客户名称id",
"条形码号"
};
/**
* 此处 Map 的 key 为每个sheet的名称,一个excel中可能有多个sheet页
* 此处 Map 中的 value List 中的 Map 的key 对应每一列的标题
* 此处 Map 中的 value list 为每个 sheet 页的数据
* 此处 HashMap 初始化时 使用默认大小 16
*/
Map>> maps = new HashMap<>(16);
//key:sheet页名称 value:数据库查出的数据集合List>
maps.put("id", list);
//需要合并的列
int[] intArr = new int[]{0,1};
ExcelUtil.createExcel(excel的头部, maps, intArr,文件名,响应头);
}
}
@Builder
@Data
class PoiModel {
private String content;
private String oldContent;
private int rowIndex;
private int cellIndex;
}