package com.example.demo.excel.utils.poi;
import com.example.demo.excel.annotation.Excel;
import com.example.demo.excel.annotation.Excel.ColumnType;
import com.example.demo.excel.annotation.Excel.Type;
import com.example.demo.excel.entity.AjaxResult;
import com.sun.xml.internal.ws.util.UtilException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.;
import java.util.;
import java.util.stream.Collectors;/**
• Excel相关处理
•
• @author ruoyi
*/
public class ExcelUtil
{
private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
public static final String FORMULA_REGEX_STR = “=|-|\+|@”;
public static final String[] FORMULA_STR = { “=”, “-”, “+”, “@” };
/**• Excel sheet最大行数,默认65536
*/
public static final int sheetSize = 65536;/**
• 工作表名称
*/
private String sheetName;/**
• 导出类型(EXPORT:导出数据;IMPORT:导入模板)
*/
private Type type;/**
• 工作薄对象
*/
private Workbook wb;/**
• 工作表对象
*/
private Sheet sheet;/**
• 样式列表
*/
private Map<String, CellStyle> styles;/**
• 导入导出数据列表
*/
private List list;/**
• 注解列表
*/
private List<Object[]> fields;/**
• 当前行号
*/
private int rownum;/**
• 标题
*/
private String title;/**
• 最大高度
*/
private short maxHeight;/**
• 合并后最后行数
*/
private int subMergedLastRowNum = 0;/**
• 合并后开始行数
*/
private int subMergedFirstRowNum = 1;/**
• 对象的子列表方法
*/
private Method subMethod;/**
• 对象的子列表属性
*/
private List subFields;/**
• 统计列表
*/
private Map<Integer, Double> statistics = new HashMap<Integer, Double>();/**
• 实体对象
*/
public Class clazz;/**
• 需要排除列属性
*/
public String[] excludeFields;public ExcelUtil(Class clazz)
{
this.clazz = clazz;
}public void init(List list, String sheetName, String title, Type type)
{
if (list == null)
{
list = new ArrayList();
}
this.list = list;
this.sheetName = sheetName;
this.type = type;
this.title = title;
createExcelField();
createWorkbook();
createTitle();
createSubHead();
}/**
• 创建excel第一行标题
*/
public void createTitle()
{
if (StringUtils.isNotEmpty(title))
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
int titleLastCol = this.fields.size() - 1;
if (isSubList())
{
titleLastCol = titleLastCol + subFields.size() - 1;
}
Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);
titleRow.setHeightInPoints(30);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get(“title”));
titleCell.setCellValue(title);
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));
}
}/**
• 创建对象的子列表名称
*/
public void createSubHead()
{
if (isSubList())
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
Row subRow = sheet.createRow(rownum);
int excelNum = 0;
for (Object[] objects : fields)
{
Excel attr = (Excel) objects[1];
Cell headCell1 = subRow.createCell(excelNum);
headCell1.setCellValue(attr.name());
excelNum++;
}
int headFirstRow = excelNum - 1;
int headLastRow = headFirstRow + subFields.size() - 1;
if (headLastRow > headFirstRow)
{
sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));
}
rownum++;
}
}/**
• 对list数据源将其里面的数据导入到excel表单
•
• @param list 导出数据集合
• @param sheetName 工作表的名称
• @return 结果
*/
public AjaxResult exportExcel(List list, String sheetName)
{
return exportExcel(list, sheetName, StringUtils.EMPTY);
}/**
• 对list数据源将其里面的数据导入到excel表单
•
• @param list 导出数据集合
• @param sheetName 工作表的名称
• @param title 标题
• @return 结果
*/
public AjaxResult exportExcel(List list, String sheetName, String title)
{
this.init(list, sheetName, title, Type.EXPORT);
return exportExcel();
}/**
• 对list数据源将其里面的数据导入到excel表单
•
• @param response 返回数据
• @param list 导出数据集合
• @param sheetName 工作表的名称
• @return 结果
*/
public void exportExcel(HttpServletResponse response, List list, String sheetName)
{
exportExcel(response, list, sheetName, StringUtils.EMPTY);
}/**
• 对list数据源将其里面的数据导入到excel表单
•
• @param response 返回数据
• @param list 导出数据集合
• @param sheetName 工作表的名称
• @param title 标题
• @return 结果
*/
public void exportExcel(HttpServletResponse response, List list, String sheetName, String title)
{
response.setContentType(“application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”);
response.setCharacterEncoding(“utf-8”);
this.init(list, sheetName, title, Type.EXPORT);
exportExcel(response);
}/**
• 对list数据源将其里面的数据导入到excel表单
•
• @return 结果
*/
public void exportExcel(HttpServletResponse response)
{
try
{
writeSheet();
wb.write(response.getOutputStream());
}
catch (Exception e)
{
log.error(“导出Excel异常{}”, e.getMessage());
}
finally
{
IOUtils.closeQuietly(wb);
}
}/**
• 对list数据源将其里面的数据导入到excel表单
•
• @return 结果
*/
public AjaxResult exportExcel()
{
OutputStream out = null;
try
{
writeSheet();
String filename = encodingFilename(sheetName);
out = new FileOutputStream(getAbsoluteFile(filename));
wb.write(out);
return AjaxResult.success(filename);
}
catch (Exception e)
{
log.error(“导出Excel异常{}”, e.getMessage());
throw new UtilException(“导出Excel失败,请联系网站管理员!”);
}
finally
{
IOUtils.closeQuietly(wb);
IOUtils.closeQuietly(out);
}
}/**
• 创建写入数据到Sheet
*/
public void writeSheet()
{
// 取出一共有多少个sheet.
int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));
for (int index = 0; index < sheetNo; index++)
{
createSheet(sheetNo, index);
// 产生一行
Row row = sheet.createRow(rownum);
int column = 0;
// 写入各个字段的列头名称
for (Object[] os : fields)
{
Field field = (Field) os[0];
Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType()))
{
for (Field subField : subFields)
{
Excel subExcel = subField.getAnnotation(Excel.class);
this.createHeadCell(subExcel, row, column++);
}
}
else
{
this.createHeadCell(excel, row, column++);
}
}
if (Type.EXPORT.equals(type))
{
fillExcelData(index, row);
}
}
}/**
• 填充excel数据
•
• @param index 序号
• @param row 单元格行
*/
@SuppressWarnings(“unchecked”)
public void fillExcelData(int index, Row row)
{
int startNo = index * sheetSize;
int endNo = Math.min(startNo + sheetSize, list.size());
int rowNo = (1 + rownum) - startNo;
for (int i = startNo; i < endNo; i++)
{
rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo;
row = sheet.createRow(rowNo);
// 得到导出对象.
T vo = (T) list.get(i);
Collection<?> subList = null;
if (isSubList())
{
if (isSubListValue(vo))
{
subList = getListCellValue(vo);
subMergedLastRowNum = subMergedLastRowNum + subList.size();
}
else
{
subMergedFirstRowNum++;
subMergedLastRowNum++;
}
}
int column = 0;
for (Object[] os : fields)
{
Field field = (Field) os[0];
Excel excel = (Excel) os[1];
if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList))
{
boolean subFirst = false;
for (Object obj : subList)
{
if (subFirst)
{
rowNo++;
row = sheet.createRow(rowNo);
}
List subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
int subIndex = 0;
for (Field subField : subFields)
{
if (subField.isAnnotationPresent(Excel.class))
{
subField.setAccessible(true);
Excel attr = subField.getAnnotation(Excel.class);
this.addCell(attr, row, (T) obj, subField, column + subIndex);
}
subIndex++;
}
subFirst = true;
}
this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();
}
else
{
this.addCell(excel, row, vo, field, column++);
}
}
}
}/**
• 创建表格样式
•
• @param wb 工作薄对象
• @return 样式列表
*/
private Map<String, CellStyle> createStyles(Workbook wb)
{
// 写入各条记录,每条记录对应excel表中的一行
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
CellStyle style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font titleFont = wb.createFont();
titleFont.setFontName(“Arial”);
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBold(true);
style.setFont(titleFont);
styles.put(“title”, style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderRight(BorderStyle.THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(BorderStyle.THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(BorderStyle.THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(BorderStyle.THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName(“Arial”);
dataFont.setFontHeightInPoints((short) 10);
style.setFont(dataFont);
styles.put(“data”, style);
style = wb.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font totalFont = wb.createFont();
totalFont.setFontName(“Arial”);
totalFont.setFontHeightInPoints((short) 10);
style.setFont(totalFont);
styles.put(“total”, style);
return styles;
}/**
• 创建单元格
*/
public Cell createHeadCell(Excel attr, Row row, int column)
{
// 创建列
Cell cell = row.createCell(column);
// 写入列信息
cell.setCellValue(attr.name());
if (isSubList())
{
if (attr.needMerge())
{
sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));
}
}
return cell;
}/**
• 设置单元格信息
•
• @param value 单元格值
• @param attr 注解相关
• @param cell 单元格信息
*/
public void setCellVo(Object value, Excel attr, Cell cell)
{
if (ColumnType.STRING == attr.cellType())
{
String cellValue = toStr(value);
// 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。
if (StringUtils.startsWithAny(cellValue, FORMULA_STR))
{
cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, “\t$0”);
}
if (value instanceof Collection && StringUtils.equals(“[]”, cellValue))
{
cellValue = StringUtils.EMPTY;
}
cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix());
}
else if (ColumnType.NUMERIC == attr.cellType())
{
if (StringUtils.isNotNull(value))
{
cell.setCellValue(StringUtils.contains(toStr(value), “.”) ? toDouble(value) : toInt(value));
}
}
}/**
• 添加单元格
*/
public Cell addCell(Excel attr, Row row, T vo, Field field, int column)
{
Cell cell = null;
try
{
// 设置行高
row.setHeight(maxHeight);
// 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
if (attr.isExport())
{
// 创建cell
cell = row.createCell(column);
if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge())
{
CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);
sheet.addMergedRegion(cellAddress);
}
// 用于读取对象中的属性
Object value = getTargetValue(vo, field, attr);
String dateFormat = attr.dateFormat();
if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value))
{
cell.setCellValue(parseDateToStr(dateFormat, value));
}
else if (value instanceof BigDecimal && -1 != attr.scale())
{
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
}
else
{
// 设置列类型
setCellVo(value, attr, cell);
}
}
}
catch (Exception e)
{
log.error(“导出Excel失败{}”, e);
}
return cell;
}/**
• 编码文件名
*/
public String encodingFilename(String filename)
{
filename = UUID.randomUUID().toString() + “_” + filename + “.xlsx”;
return filename;
}/**
• 获取下载路径
•
• @param filename 文件名称
*/
public String getAbsoluteFile(String filename)
{
String downloadPath = “D:\” + filename;
File desc = new File(downloadPath);
if (!desc.getParentFile().exists())
{
desc.getParentFile().mkdirs();
}
return downloadPath;
}/**
• 获取bean中的属性值
•
• @param vo 实体对象
• @param field 字段
• @param excel 注解
• @return 最终的属性值
• @throws Exception
*/
private Object getTargetValue(T vo, Field field, Excel excel) throws Exception
{
Object o = field.get(vo);
if (StringUtils.isNotEmpty(excel.targetAttr()))
{
String target = excel.targetAttr();
if (target.contains(“.”))
{
String[] targets = target.split(“[.]”);
for (String name : targets)
{
o = getValue(o, name);
}
}
else
{
o = getValue(o, target);
}
}
return o;
}/**
• 以类的属性的get方法方法形式获取值
•
• @param o
• @param name
• @return value
• @throws Exception
*/
private Object getValue(Object o, String name) throws Exception
{
if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
{
Class<?> clazz = o.getClass();
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
o = field.get(o);
}
return o;
}/**
• 得到所有定义字段
*/
private void createExcelField()
{
this.fields = getFields();
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
this.maxHeight = getRowHeight();
}/**
• 获取字段注解信息
*/
public List<Object[]> getFields()
{
List<Object[]> fields = new ArrayList<Object[]>();
List tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
for (Field field : tempFields)
{
if (!ArrayUtils.contains(this.excludeFields, field.getName()))
{
// 单注解
if (field.isAnnotationPresent(Excel.class))
{
Excel attr = field.getAnnotation(Excel.class);
if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
{
field.setAccessible(true);
fields.add(new Object[] { field, attr });
}
if (Collection.class.isAssignableFrom(field.getType()))
{
subMethod = getSubMethod(field.getName(), clazz);
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
}
}
}
}
return fields;
}/**
• 根据注解获取最大行高
*/
public short getRowHeight()
{
double maxHeight = 0;
for (Object[] os : this.fields)
{
Excel excel = (Excel) os[1];
maxHeight = Math.max(maxHeight, excel.height());
}
return (short) (maxHeight * 20);
}/**
• 创建一个工作簿
*/
public void createWorkbook()
{
this.wb = new SXSSFWorkbook(500);
this.sheet = wb.createSheet();
wb.setSheetName(0, sheetName);
this.styles = createStyles(wb);
}/**
• 创建工作表
•
• @param sheetNo sheet数量
• @param index 序号
*/
public void createSheet(int sheetNo, int index)
{
// 设置工作表的名称.
if (sheetNo > 1 && index > 0)
{
this.sheet = wb.createSheet();
this.createTitle();
wb.setSheetName(index, sheetName + index);
}
}/**
• 格式化不同类型的日期对象
•
• @param dateFormat 日期格式
• @param val 被格式化的日期对象
• @return 格式化后的日期字符
*/
public String parseDateToStr(String dateFormat, Object val)
{
if (val == null)
{
return “”;
}
String str;
if (val instanceof Date)
{
str = DateToStr(dateFormat, (Date) val);
}
else if (val instanceof LocalDateTime)
{
str = DateToStr(dateFormat, toDate((LocalDateTime) val));
}
else if (val instanceof LocalDate)
{
str = DateToStr(dateFormat, toDate((LocalDate) val));
}
else
{
str = val.toString();
}
return str;
}/**
• 是否有对象的子列表
*/
public boolean isSubList()
{
return StringUtils.isNotNull(subFields) && subFields.size() > 0;
}/**
• 是否有对象的子列表,集合不为空
*/
public boolean isSubListValue(T vo)
{
return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0;
}/**
• 获取集合的值
*/
public Collection<?> getListCellValue(Object obj) { Object value; try { value = subMethod.invoke(obj, new Object[] {}); } catch (Exception e) { return new ArrayList(); } return (Collection<?>) value;
}/**
• 获取对象的子列表方法
•
• @param name 名称
• @param pojoClass 类对象
• @return 子列表方法
/
public Method getSubMethod(String name, Class<?> pojoClass)
{
StringBuffer getMethodName = new StringBuffer(“get”);
getMethodName.append(name.substring(0, 1).toUpperCase());
getMethodName.append(name.substring(1));
Method method = null;
try
{
method = pojoClass.getMethod(getMethodName.toString(), new Class[] {});
}
catch (Exception e)
{
log.error(“获取对象异常{}”, e.getMessage());
}
return method;
}
/*• 转换为字符串
• 如果给定的值为null,或者转换失败,返回默认值null
• 转换失败不会报错
•
• @param value 被转换的值
• @return 结果
*/
public String toStr(Object value)
{
return toStr(value, null);
}/**
• 转换为字符串
• 如果给定的值为null,或者转换失败,返回默认值
• 转换失败不会报错
•
• @param value 被转换的值
• @param defaultValue 转换错误时的默认值
• @return 结果
*/
public String toStr(Object value, String defaultValue)
{
if (null == value)
{
return defaultValue;
}
if (value instanceof String)
{
return (String) value;
}
return value.toString();
}/**
• 转换为double
• 如果给定的值为空,或者转换失败,返回默认值null
• 转换失败不会报错
•
• @param value 被转换的值
• @return 结果
*/
public Double toDouble(Object value)
{
return toDouble(value, null);
}/**
• 转换为double
• 如果给定的值为空,或者转换失败,返回默认值
• 转换失败不会报错
•
• @param value 被转换的值
• @param defaultValue 转换错误时的默认值
• @return 结果
/
public Double toDouble(Object value, Double defaultValue)
{
if (value == null)
{
return defaultValue;
}
if (value instanceof Double)
{
return (Double) value;
}
if (value instanceof Number)
{
return ((Number) value).doubleValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr))
{
return defaultValue;
}
try
{
// 支持科学计数法
return new BigDecimal(valueStr.trim()).doubleValue();
}
catch (Exception e)
{
return defaultValue;
}
}
/*• 转换为int
• 如果给定的值为null,或者转换失败,返回默认值null
• 转换失败不会报错
•
• @param value 被转换的值
• @return 结果
*/
public Integer toInt(Object value)
{
return toInt(value, null);
}/**
• 转换为int
• 如果给定的值为空,或者转换失败,返回默认值
• 转换失败不会报错
•
• @param value 被转换的值
• @param defaultValue 转换错误时的默认值
• @return 结果
*/
public Integer toInt(Object value, Integer defaultValue)
{
if (value == null)
{
return defaultValue;
}
if (value instanceof Integer)
{
return (Integer) value;
}
if (value instanceof Number)
{
return ((Number) value).intValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr))
{
return defaultValue;
}
try
{
return Integer.parseInt(valueStr.trim());
}
catch (Exception e)
{
return defaultValue;
}
}public String DateToStr(String format, Date date)
{
return new SimpleDateFormat(format).format(date);
}/**
• 增加 LocalDateTime ==> Date
*/
public Date toDate(LocalDateTime temporalAccessor)
{
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}/**
• 增加 LocalDate ==> Date
*/
public Date toDate(LocalDate temporalAccessor)
{
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
}