最近老是遇到需要导出excel的业务,觉得一直cv太麻烦了,而且poi的业务重复度很高,影响阅读,自己封装了一个excel处理类,目前暂时只支持普通的数据导出,复杂的单元格合并的情况暂时没有处理。
废话不多说,直接贴代码,主要作为自己的笔记记录,大佬勿喷!有需要者自取
maven依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
ExcelExportParam.java
用于设定导出的参数
package com.pub.bean;
import lombok.Data;
import net.sf.json.JSONArray;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
/**
* @Description: [ExcelExportParam]
* @Author: Luo Ly
* @Date: 2022/5/7 16:21
*/
@Data
public class ExcelExportParam {
private String title; //表格标题
private String fileName; //文件名称
private JSONArray data; //数据主体
private String[] head; //表头
private String[] cellKey; //单元格中对应表头的字段名(注意:必须与表头对应,否则数据填充错乱)
private int[] cellWidth; //单元格中对应表头的字段宽度(注意:必须与表头对应,否则数据填充错乱,单位为:字符)
private Boolean hasNumber; //是否添加序号
private HSSFCellStyle titleStyle; //标题样式(不设置则使用默认样式)
private HSSFCellStyle headStyle; //表头样式(不设置则使用默认样式)
private HSSFCellStyle cellStyle; //单元格样式(不设置则使用默认样式)
}
ExcelExportService.java
poi的处理类,准备后期新增其他处理方式的,目前只支持普通数据
package com.pub.service;
import com.pub.bean.ExcelExportParam;
import com.utils.ExcelUtils;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
/**
* @Description: [ExcelExportService]
* @Author: Luo Ly
* @Date: 2022/5/7 16:21
*/
public class ExcelExportService {
/**
*常规文件导出(适用于无单元格合并,无特殊数据处理的普通数据列表 )
* @param response
* @param param 参数配置
* @throws Exception
*/
public static void exportExcelNormal(HttpServletResponse response,ExcelExportParam param) throws Exception{
//参数校验
if(param.getHead()==null || param.getHead().length<=0){
System.err.println("表头参数【head】不完整!");
return;
}
if(param.getCellKey()==null || param.getCellKey().length<=0){
System.err.println("字段参数【cellKey】不完整!");
return;
}
if(param.getCellWidth()==null || param.getCellWidth().length<=0){
System.err.println("单元格宽度参数【cellWidth】不完整!");
return;
}
if(param.getHasNumber()==null){
param.setHasNumber(false);
}
HSSFWorkbook workbook = new HSSFWorkbook();// 产生工作薄对象
HSSFSheet sheet = workbook.createSheet("sheet");// 产生工作表对象
HSSFCellStyle titleStyle = workbook.createCellStyle(); // 标题
HSSFCellStyle headStyle = workbook.createCellStyle(); // 表头
HSSFCellStyle cellStyle = workbook.createCellStyle(); // 表格内容
if(param.getTitleStyle()!=null){
titleStyle=param.getTitleStyle();
}else{
titleStyle= ExcelUtils.getTitleStyle(workbook);
}
if(param.getHeadStyle()!=null){
headStyle=param.getHeadStyle();
}else{
headStyle= ExcelUtils.getHeadStyle(workbook);
}
if(param.getCellStyle()!=null){
cellStyle=param.getCellStyle();
}else{
cellStyle= ExcelUtils.getCellStyle(workbook);
}
// 设置标题栏
HSSFRow titleRow = sheet.createRow(0);
if(param.getHasNumber()){
CellRangeAddress cra = new CellRangeAddress(0, 0, 0, param.getHead().length);
sheet.addMergedRegion(cra);
}else{
CellRangeAddress cra = new CellRangeAddress(0, 0, 0, param.getHead().length - 1);
sheet.addMergedRegion(cra);
}
titleRow.setHeight((short) (40 * 20));
HSSFCell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(titleStyle);
titleCell.setCellValue(param.getTitle());
// 表头
HSSFRow row = sheet.createRow(1);
if(param.getHasNumber()){
String []newHead=new String[param.getHead().length+1];
int [] newWidth=new int[param.getCellWidth().length+1];
for (int i = 0; i < newHead.length; i++) {
if(i==0){
newHead[0]="序号";
newWidth[0]=5;
}else{
newHead[i]=param.getHead()[i-1];
newWidth[i]=param.getCellWidth()[i-1];
}
}
param.setHead(newHead);
param.setCellWidth(newWidth);
}
for (int i = 0; i < param.getHead().length; i++) {
row.setHeight((short) (25 * 20));
HSSFCell headCell = row.createCell(i);
headCell.setCellStyle(headStyle);
headCell.setCellValue(param.getHead()[i]);
sheet.setColumnWidth(i, ExcelUtils.formatWidth2Char(param.getCellWidth()[i]));
}
//数据主体
JSONArray data=param.getData();
for (int i = 0; i < data.size(); i++) {
JSONObject item=data.getJSONObject(i);
HSSFRow itemRow = sheet.createRow(i+2); //除去标题和表头的序列
itemRow.setHeightInPoints(25);
if(param.getHasNumber()){
HSSFCell cell = itemRow.createCell(0);
cell.setCellStyle(cellStyle);
cell.setCellValue(i + 1);
}
for (int i1 = 0; i1 < param.getCellKey().length; i1++) {
String key=param.getCellKey()[i1];
if(item.has(key)){
int cellIndex=param.getHasNumber()?i1+1:i1; //当前列的序列
HSSFCell cell = itemRow.createCell(cellIndex);
cell.setCellStyle(cellStyle);
cell.setCellValue(item.getString(key));
}else{
System.err.println("数据【"+i+"】不存在"+key);
}
}
}
//为作用单元格加上边框
ExcelUtils.setBorder(sheet,true,0,data.size(),0,param.getHead().length);
// 设置response方式,使执行此controller时候自动出现下载页面,而非直接使用excel打开
response.reset();
response.setContentType("APPLICATION/vnd.ms-excel");
// 注意,如果去掉下面一行代码中的attachment; 那么也会使IE自动打开文件。
response.setHeader("Content-Disposition", "attachment;filename=\""
+ new String(param.getFileName().getBytes("gb2312"), "ISO8859-1")
+ ".xls\"");
OutputStream os = response.getOutputStream();
workbook.write(os);
os.flush();
os.close();
}
}
ExcelUtils.java
业务过程中一些的工具,单元格高宽换算的部分是在网上大佬的代码中cv过来魔改的,查了很多资料很难做到统一。然后我就根据实际导出效果来计算的大概数据,如果在业务中出现高宽异常的情况,可能需要调整一下这部分的逻辑
package com.utils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import sofun.util.StringUtil;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* POI excel 工具集
*
* @Description: [ExcelUtils]
* @Author: Luo Ly
* @Date: 2022/5/7 15:21
*/
public class ExcelUtils {
/**
* 合并指定单元格并设置边框
*
* @param sheet
* @param startRow
* @param endRow
* @param startCell
* @param endCell
* @date 2022年5月6日16:43:58
* @author lly
*/
public static void mergedRegion(HSSFSheet sheet, int startRow, int endRow, int startCell, int endCell) {
CellRangeAddress cra = new CellRangeAddress(startRow, endRow, startCell, endCell);
sheet.addMergedRegion(cra);
RegionUtil.setBorderLeft(BorderStyle.THIN, cra, sheet);
RegionUtil.setBorderRight(BorderStyle.THIN, cra, sheet);
RegionUtil.setBorderTop(BorderStyle.THIN, cra, sheet);
RegionUtil.setBorderBottom(BorderStyle.THIN, cra, sheet);
}
/**
* 给指定单元格加上边框
*
* @param sheet
* @param autoRowHeight 是否自定调整行高
* @param startRow 开始行
* @param endRow 结束行
* @param startCell 开始列
* @param endCell 结束列
*/
public static void setBorder(HSSFSheet sheet, boolean autoRowHeight, int startRow, int endRow, int startCell, int endCell) {
for (int i = startRow; i < endRow; i++) {
for (int j = startCell; j < endCell; j++) {
CellRangeAddress cra = new CellRangeAddress(i, i, j, j);
RegionUtil.setBorderLeft(BorderStyle.THIN, cra, sheet);
RegionUtil.setBorderRight(BorderStyle.THIN, cra, sheet);
RegionUtil.setBorderTop(BorderStyle.THIN, cra, sheet);
RegionUtil.setBorderBottom(BorderStyle.THIN, cra, sheet);
HSSFCell cell = sheet.getRow(i).getCell(j);
if (cell != null && autoRowHeight) {
autoRowHeight(cell, (short) 0);
}
}
}
}
/**
* 获取默认标题样式
*
* @param workbook
* @return
*/
public static HSSFCellStyle getTitleStyle(HSSFWorkbook workbook) {
HSSFCellStyle titleStyle = workbook.createCellStyle();
HSSFFont titleFont = workbook.createFont();
titleFont.setFontHeightInPoints((short) 20); // 字号
titleFont.setBold(true);
titleStyle.setFont(titleFont);
titleStyle.setBorderBottom(BorderStyle.THIN);
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
return titleStyle;
}
/**
* 获取默认表头样式
*
* @param workbook
* @return
*/
public static HSSFCellStyle getHeadStyle(HSSFWorkbook workbook) {
HSSFCellStyle headStyle = workbook.createCellStyle();
HSSFFont headFont = workbook.createFont();
headFont.setFontHeightInPoints((short) 15); // 字号
headFont.setBold(true);
headStyle.setFont(headFont);
headStyle.setBorderBottom(BorderStyle.THIN);
headStyle.setBorderTop(BorderStyle.THIN);
headStyle.setBorderRight(BorderStyle.THIN);
headStyle.setBorderLeft(BorderStyle.THIN);
headStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
headStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());// 设置25灰度背景颜色
// headStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
return headStyle;
}
/**
* 获取默认单元格样式
*
* @param workbook
* @return
*/
public static HSSFCellStyle getCellStyle(HSSFWorkbook workbook) {
HSSFCellStyle cellStyle = workbook.createCellStyle();
HSSFFont cellFont = workbook.createFont();
cellFont.setFontHeightInPoints((short) 10); // 字号
cellFont.setBold(true);
cellStyle.setFont(cellFont);
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
cellStyle.setWrapText(true); //自动换行
return cellStyle;
}
/**
* 自适应excel行高
*
* @param cell
* @param margin
*/
public static void autoRowHeight(HSSFCell cell, short margin) {
if (cell == null) {
return;
}
String cellVal = getStringCellValue(cell);
if (StringUtil.isEmpty(cellVal)) {
return;
}
HSSFRow row = cell.getRow();
HSSFWorkbook workbook = row.getSheet().getWorkbook();
// pt = px*72/96 = px*3/4
//换算思路如下(根据使用情况测出,与实际单位换算有出入):
// 宽度计算
// 字体12px时 50宽 可容纳22个字
// 每个字占宽约 2.28宽
// 字体每px占约0.19宽度
// 宽度20的单元格 可容纳字数应为:20/(12*0.19) = 8.78个字
// 高度计算
// 字体为12px时 高30pt的单元格可容纳2行
// 单行占高 30/2=15pt
// 字体每px约占15/12=1.25pt高度
// 2行应占高度为 1.25*12*2=30pt;
float fontSize = cell.getCellStyle().getFont(workbook).getFontHeightInPoints(); // 字体大小
float cellWidth = getCellWidth(cell);// 单元格宽度
int fontForCellWidth = (int) (cellWidth / (fontSize * 0.19)); // 每行可容纳字数
if (fontForCellWidth <= 0) {
return;
}
int fontLength = cellVal.length();
// 计算内容中的换行
String v = cellVal.replaceAll("\\n|\\r", "");
// 单元格内换行符出现的次数
int line = fontLength - v.length();
int rowNum = fontLength / fontForCellWidth + (fontLength % fontForCellWidth > 0 ? 1 : 0);// 分行
rowNum += line;
short cellHeight = (short) ((1.25 * fontSize + margin) * rowNum);
float oldH = row.getHeightInPoints();
if (cellHeight > oldH) {
row.setHeightInPoints(cellHeight);
}
}
private static String getStringCellValue(Cell cell) {
if (cell.getCellType() == CellType.FORMULA) {
if (cell.getCellStyle().getDataFormat() == HSSFDataFormat.getBuiltinFormat("yyyy-MM-dd")) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(HSSFDateUtil.getJavaDate(cell.getNumericCellValue()));
}
return cell.getCellFormula();
} else if (cell.getCellType() == CellType.STRING) {
return cell.getStringCellValue();
} else if (cell.getCellType() == CellType.NUMERIC) {
if (HSSFDateUtil.isCellDateFormatted(cell)) {
String dateStr = "";
int style = cell.getCellStyle().getDataFormat();
Date date = cell.getDateCellValue();
// 对不一样格式的日期类型作不一样的输出,与单元格格式保持一致
switch (style) {
case 178:
dateStr = new SimpleDateFormat("yyyy'年'M'月'd'日'").format(date);
break;
case 14:
dateStr = new SimpleDateFormat("yyyy-MM-dd").format(date);
break;
case 179:
dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm").format(date);
break;
case 181:
dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm a ").format(date);
break;
case 22:
dateStr = new SimpleDateFormat(" yyyy/MM/dd HH:mm:ss ").format(date);
break;
default:
break;
}
return dateStr;
} else {
cell.setCellType(CellType.STRING);
}
return cell.getStringCellValue();
} else if (cell.getCellType() == CellType.BOOLEAN) {
return cell.getBooleanCellValue() ? "TRUE" : "FALSE";
} else {
return "";
}
}
/**
* 字符长度转poi长度
*
* @param columnWidth
* @return
*/
public static float formatWidth(float columnWidth) {
columnWidth = (float) ((columnWidth / 256) - 0.72);
return columnWidth;
}
/**
* 转字符长度,输出数值与excel中数值一直(有微弱偏差,可忽略)
*
* @param num
* @return
*/
public static int formatWidth2Char(float num) {
return (int) ((num + 0.72) * 256);
}
private static float getCellWidth(HSSFCell cell) {
if (cell == null) {
return 0;
}
HSSFSheet sheet = cell.getSheet();
int rowIndex = cell.getRowIndex();
int columnIndex = cell.getColumnIndex();
// float width = sheet.getColumnWidth(columnIndex);
float width = formatWidth(sheet.getColumnWidth(columnIndex));
boolean isPartOfRegion = false;
int firstColumn = 0;
int lastColumn = 0;
int firstRow = 0;
int lastRow = 0;
int sheetMergeCount = sheet.getNumMergedRegions();
for (int i = 0; i < sheetMergeCount; i++) {
CellRangeAddress ca = sheet.getMergedRegion(i);
firstColumn = ca.getFirstColumn();
lastColumn = ca.getLastColumn();
firstRow = ca.getFirstRow();
lastRow = ca.getLastRow();
if (rowIndex == firstRow && rowIndex <= lastRow) {
if (columnIndex == firstColumn && columnIndex <= lastColumn) {
isPartOfRegion = true;
break;
}
}
}
if (isPartOfRegion) {
width = 0;
for (int i = firstColumn; i <= lastColumn; i++) {
// width += sheet.getColumnWidth(i);
width += formatWidth(sheet.getColumnWidth(i));
}
}
return width;
}
}
调用范例
懒得写假数据了,前面几行获取数据的代码自行替换
组织装ExcelExportParam 然后直接调用就OK了
方便处理 data使用的JSONArray 如果业务不允许需要自行修改service
@RequestMapping("export")
public void export(HttpServletRequest request, HttpServletResponse response) {
try {
//必须转码,不然会导致乱码查询参数错误
request.setCharacterEncoding("UTF-8");
String exportTitle=request.getParameter("exportTitle");
JSONObject queryParam = super.getQueryParam(request);
PageResult pageResult=super.pageQuery(1,9999,queryParam);
JSONArray data=JSONArray.fromObject(pageResult.getData());
//表头
String []head=new String[]{"标题","文件类型","流水号","状态","接收人姓名","接收人部门"};
//表头对应字段名
String []cellKey=new String[]{"title","type","documentNumber","state","receiveName","receiveDeptName"};
//表头对应单元格宽度
int []cellWidth=new int[]{60,20,15,30,20,10};
ExcelExportParam eep=new ExcelExportParam();
eep.setFileName(exportTitle);
eep.setTitle(exportTitle);
eep.setHasNumber(true);
eep.setHead(head);
eep.setCellKey(cellKey);
eep.setCellWidth(cellWidth);
eep.setData(data);
ExcelExportService.exportExcelNormal(response,eep);
}catch (Exception e){
e.printStackTrace();
System.err.println("导出失败!");
}
}