2025-04-25 09:12:09 +08:00

633 lines
25 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.eden.room.utils;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.io.IOException;
import com.alibaba.excel.EasyExcel;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import com.eden.room.utils.excel.Excel;
import javax.servlet.http.HttpServletResponse;
/**
* Excel相关处理工具类
*
* @param <T> Excel导入导出对象类型
*/
public class ExcelUtil<T> {
private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
/**
* 实体类型
*/
private Class<T> clazz;
/**
* 初始化
*
* @param clazz Excel导入导出对象类型
*/
public ExcelUtil(Class<T> clazz) {
this.clazz = clazz;
}
/**
* 获取单元格的值
*
* @param cell 单元格
* @param fieldType 字段类型
* @return 单元格值
*/
private Object getCellValue(Cell cell, Class<?> fieldType) {
Object value = null;
switch (cell.getCellType()) {
case STRING:
String cellValue = cell.getStringCellValue();
if (StringUtils.hasText(cellValue) && !cellValue.trim().isEmpty()) {
value = convertValue(cellValue, fieldType);
}
break;
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
// 日期类型
value = cell.getDateCellValue();
} else {
// 数值类型
double numericValue = cell.getNumericCellValue();
if (fieldType == BigDecimal.class) {
value = BigDecimal.valueOf(numericValue);
} else if (fieldType == Long.class || fieldType == long.class) {
value = (long) numericValue;
} else if (fieldType == Integer.class || fieldType == int.class) {
value = (int) numericValue;
} else if (fieldType == Double.class || fieldType == double.class) {
value = numericValue;
} else if (fieldType == Float.class || fieldType == float.class) {
value = (float) numericValue;
} else if (fieldType == String.class) {
// 防止科学计数法
// 判断是否为整数
if (numericValue == Math.floor(numericValue) && !Double.isInfinite(numericValue)) {
// 为整数,去掉小数点和小数部分
value = String.valueOf((long) numericValue);
} else {
// 为小数,按原样输出
value = String.valueOf(numericValue);
}
} else {
value = numericValue;
}
}
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case FORMULA:
try {
value = cell.getStringCellValue();
} catch (Exception e) {
value = cell.getNumericCellValue();
}
break;
default:
break;
}
return value;
}
/**
* 转换字符串为指定类型
*
* @param value 字符串值
* @param fieldType 目标类型
* @return 转换后的值
*/
private Object convertValue(String value, Class<?> fieldType) {
if (fieldType == String.class) {
// 检查字符串是否为数字表示
try {
if (value.contains(".")) {
// 尝试将其解析为double
double doubleValue = Double.parseDouble(value);
// 检查是否为整数
if (doubleValue == Math.floor(doubleValue) && !Double.isInfinite(doubleValue)) {
// 为整数,去掉小数点和小数部分
return String.valueOf((long) doubleValue);
}
}
} catch (NumberFormatException e) {
// 不是数字,直接返回原字符串
}
return value;
} else if (fieldType == Integer.class || fieldType == int.class) {
return Integer.parseInt(value);
} else if (fieldType == Long.class || fieldType == long.class) {
return Long.parseLong(value);
} else if (fieldType == Double.class || fieldType == double.class) {
return Double.parseDouble(value);
} else if (fieldType == Float.class || fieldType == float.class) {
return Float.parseFloat(value);
} else if (fieldType == BigDecimal.class) {
return new BigDecimal(value);
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
return Boolean.parseBoolean(value);
}
return value;
}
/**
* 导出Excel
*
* @param list 导出数据集合
* @param sheetName 工作表名称
* @param os 输出流
* @throws Exception 异常
*/
public void exportExcel(List<T> list, String sheetName, OutputStream os) throws Exception {
// 创建工作簿
Workbook workbook = new XSSFWorkbook();
try {
// 创建工作表
Sheet sheet = workbook.createSheet(sheetName);
// 设置默认列宽
sheet.setDefaultColumnWidth(15);
// 设置表头样式
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
// 设置数据行样式
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
// 获取所有字段
Field[] fields = clazz.getDeclaredFields();
List<Field> exportFields = new ArrayList<>();
// 筛选带有@Excel注解的字段
for (Field field : fields) {
Excel excel = field.getAnnotation(Excel.class);
if (excel != null && excel.isExport()) {
field.setAccessible(true);
exportFields.add(field);
}
}
// 按照sort排序
exportFields.sort((f1, f2) -> {
Excel e1 = f1.getAnnotation(Excel.class);
Excel e2 = f2.getAnnotation(Excel.class);
return Integer.compare(e1.sort(), e2.sort());
});
// 创建标题行
Row headerRow = sheet.createRow(0);
// 创建标题
for (int i = 0; i < exportFields.size(); i++) {
Field field = exportFields.get(i);
Excel excel = field.getAnnotation(Excel.class);
Cell cell = headerRow.createCell(i);
cell.setCellStyle(headerStyle);
cell.setCellValue(excel.name());
}
// 填充数据
if (list != null && !list.isEmpty()) {
for (int i = 0; i < list.size(); i++) {
Row row = sheet.createRow(i + 1);
T entity = list.get(i);
for (int j = 0; j < exportFields.size(); j++) {
Field field = exportFields.get(j);
Excel excel = field.getAnnotation(Excel.class);
Cell cell = row.createCell(j);
cell.setCellStyle(dataStyle);
Object value = field.get(entity);
if (value == null) {
cell.setCellValue("");
continue;
}
// 处理日期类型
if (value instanceof Date) {
if (excel.dateFormat().length() > 0) {
cell.setCellValue(new java.text.SimpleDateFormat(excel.dateFormat()).format((Date) value));
} else {
cell.setCellValue(new java.text.SimpleDateFormat("yyyy-MM-dd").format((Date) value));
}
continue;
}
// 处理数值类型
if (excel.cellType() == Excel.ColumnType.NUMERIC) {
if (value instanceof Integer) {
cell.setCellValue(((Integer) value).doubleValue());
} else if (value instanceof Long) {
cell.setCellValue(((Long) value).doubleValue());
} else if (value instanceof Double) {
cell.setCellValue((Double) value);
} else if (value instanceof Float) {
cell.setCellValue(((Float) value).doubleValue());
} else if (value instanceof BigDecimal) {
cell.setCellValue(((BigDecimal) value).doubleValue());
}
// 添加后缀
if (excel.suffix().length() > 0) {
String stringValue = value.toString() + excel.suffix();
cell.setCellValue(stringValue);
}
} else {
// 处理枚举值转换
String cellValue = value.toString();
if (excel.readConverterExp().length() > 0) {
cellValue = convertByExp(cellValue, excel.readConverterExp(), true);
}
cell.setCellValue(cellValue);
}
}
}
}
// 写入输出流
workbook.write(os);
} finally {
if (workbook != null) {
try {
workbook.close();
} catch (IOException e) {
log.error("关闭Excel工作簿异常", e);
}
}
}
}
/**
* 根据表达式转换值
*
* @param value 原始值
* @param converterExp 转换表达式例如0=男,1=女,2=未知)
* @param reverse 是否反向转换
* @return 转换后的值
*/
private String convertByExp(String value, String converterExp, boolean reverse) {
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (itemArray.length == 2) {
if (reverse) {
// 反向转换(从值转换为键)
if (itemArray[1].equals(value)) {
return itemArray[0];
}
} else {
// 正向转换(从键转换为值)
if (itemArray[0].equals(value)) {
return itemArray[1];
}
}
}
}
return value;
}
/**
* 导入Excel数据
*
* @param is Excel文件输入流
* @return 导入的数据列表
* @throws Exception 导入异常
*/
public List<T> importExcel(InputStream is) throws Exception {
List<T> list = new ArrayList<>();
Workbook workbook = null;
try {
log.info("开始解析Excel文件...");
// 尝试创建工作簿
try {
workbook = WorkbookFactory.create(is);
} catch (Exception e) {
log.error("创建工作簿失败", e);
throw new IOException("Excel文件格式错误或文件已损坏" + e.getMessage());
}
// 检查工作表
if (workbook.getNumberOfSheets() == 0) {
throw new IOException("Excel文件中没有工作表");
}
Sheet sheet = workbook.getSheetAt(0);
log.info("工作表名称: {}, 总行数: {}", sheet.getSheetName(), sheet.getPhysicalNumberOfRows());
// 获取标题行
Row headerRow = sheet.getRow(1);
if (headerRow == null) {
throw new IOException("Excel文件格式错误缺少标题行");
}
log.info("标题行单元格数: {}", headerRow.getPhysicalNumberOfCells());
// 获取带有@Excel注解的字段
Field[] fields = clazz.getDeclaredFields();
Map<Integer, Field> fieldMap = new HashMap<>();
Map<Integer, Excel> excelAnnotationMap = new HashMap<>();
Map<String, Field> fieldNameMap = new HashMap<>();
Map<String, Excel> excelNameMap = new HashMap<>();
// 预先构建字段名称映射表
for (Field field : fields) {
Excel excel = field.getAnnotation(Excel.class);
if (excel != null) {
field.setAccessible(true);
fieldNameMap.put(excel.name().trim(), field);
excelNameMap.put(excel.name().trim(), excel);
log.debug("Excel注解字段: {}, 名称: {}", field.getName(), excel.name());
}
}
if (fieldNameMap.isEmpty()) {
throw new IOException("未找到带有@Excel注解的字段无法导入数据");
}
// 解析标题行,将字段与列索引匹配
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
Cell cell = headerRow.getCell(i);
if (cell == null) {
continue;
}
String headerValue = "";
try {
// 尝试获取标题值,处理不同类型的单元格
if (cell.getCellType() == CellType.STRING) {
headerValue = cell.getStringCellValue().trim();
} else if (cell.getCellType() == CellType.NUMERIC) {
headerValue = String.valueOf(cell.getNumericCellValue()).trim();
} else {
headerValue = cell.toString().trim();
}
} catch (Exception e) {
log.warn("获取标题单元格值异常,跳过第{}列", i + 1, e);
continue;
}
if (StringUtils.isEmpty(headerValue)) {
continue;
}
// 查找匹配的字段
Field field = fieldNameMap.get(headerValue);
Excel excel = excelNameMap.get(headerValue);
if (field != null && excel != null) {
fieldMap.put(i, field);
excelAnnotationMap.put(i, excel);
log.info("匹配到列: {}, 对应字段: {}", headerValue, field.getName());
} else {
log.warn("列[{}]没有对应的字段注解", headerValue);
}
}
if (fieldMap.isEmpty()) {
throw new IOException("Excel文件中的列名与定义的@Excel注解不匹配请检查模板格式");
}
// 解析数据行
int rowCount = sheet.getLastRowNum() + 1;
log.info("开始解析数据行,共{}行", rowCount - 1);
for (int i = 3; i < rowCount; i++) {
Row row = sheet.getRow(i);
if (row == null) {
log.warn("第{}行为空,已跳过", i + 1);
continue;
}
// 检查是否为空白行(所有单元格都为空)
if (isRowEmpty(row)) {
log.warn("第{}行所有单元格为空,已跳过", i + 1);
continue;
}
// 创建实例
T entity = clazz.newInstance();
boolean hasData = false;
// 遍历列
for (int j = 0; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
// 获取对应的字段
Field field = fieldMap.get(j);
Excel excel = excelAnnotationMap.get(j);
if (field != null && excel != null) {
try {
// 获取单元格值
Object value = null;
if (cell != null) {
// 特殊处理楼层字段
if (field.getName().equalsIgnoreCase("floorNumber") ||
(excel.name() != null && (excel.name().contains("楼层") || excel.name().contains("层数")))) {
if (field.getType() == String.class) {
// 如果是字符串类型的楼层字段,使用特殊处理方法
value = getIntegerStringFromCell(cell);
log.debug("特殊处理楼层字段: {}, 原值: {}, 处理后: {}", field.getName(), cell, value);
} else {
value = getCellValue(cell, field.getType());
}
} else {
value = getCellValue(cell, field.getType());
}
}
// 必填字段校验
if (excel.required() && (value == null || value.toString().trim().isEmpty())) {
throw new IOException("" + (i + 1) + "" + excel.name() + "不能为空");
}
// 处理枚举转换
if (value != null && !StringUtils.isEmpty(excel.readConverterExp())) {
try {
value = convertByExp(value.toString(), excel.readConverterExp(), false);
} catch (Exception e) {
log.warn("第{}行{}列值[{}]转换异常", i + 1, excel.name(), value, e);
}
}
// 设置字段值
if (value != null) {
field.set(entity, value);
hasData = true;
log.debug("第{}行{}列设置值: {}", i + 1, excel.name(), value);
}
} catch (Exception e) {
log.error("解析第{}行{}列发生异常", i + 1, excel.name(), e);
throw new IOException("" + (i + 1) + "" + excel.name() + "解析失败: " + e.getMessage());
}
}
}
// 添加到列表
if (hasData) {
list.add(entity);
log.debug("第{}行数据已添加", i + 1);
} else {
log.warn("第{}行没有有效数据,已跳过", i + 1);
}
}
log.info("Excel导入完成共解析{}条记录", list.size());
} catch (Exception e) {
log.error("Excel导入异常", e);
if (e instanceof IOException) {
throw e;
} else {
throw new IOException("Excel导入失败: " + e.getMessage(), e);
}
} finally {
if (workbook != null) {
try {
workbook.close();
} catch (IOException e) {
log.error("关闭Excel工作簿异常", e);
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.error("关闭输入流异常", e);
}
}
}
return list;
}
/**
* 导出Excel到HttpServletResponse
*
* @param response HTTP响应对象
* @param fileName 文件名(不含扩展名)
* @param sheetName 工作表名称
* @throws Exception 异常
*/
public void exportExcel(HttpServletResponse response, String fileName, String sheetName) throws Exception {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String downloadFileName = java.net.URLEncoder.encode(fileName, "UTF-8") + ".xlsx";
response.setHeader("Content-disposition", "attachment;filename=" + downloadFileName);
// 创建一个空的列表,只导出表头
exportExcel(new ArrayList<>(), sheetName, response.getOutputStream());
}
/**
* 检查行是否为空
*
* @param row 要检查的行
* @return 如果行所有单元格都为空则返回true否则返回false
*/
private boolean isRowEmpty(Row row) {
for (int i = 0; i < row.getLastCellNum(); i++) {
Cell cell = row.getCell(i);
if (cell != null && cell.getCellType() != CellType.BLANK) {
return false;
}
}
return true;
}
/**
* 处理数值型单元格为整数字符串
* 特别用于处理楼层编号等需要保持为整数形式的字段
*
* @param cell 单元格
* @return 整数形式的字符串
*/
private String getIntegerStringFromCell(Cell cell) {
if (cell == null) {
return null;
}
String result = null;
switch (cell.getCellType()) {
case STRING:
String strValue = cell.getStringCellValue().trim();
if (!strValue.isEmpty()) {
try {
// 尝试解析为数字
double doubleValue = Double.parseDouble(strValue);
if (doubleValue == Math.floor(doubleValue) && !Double.isInfinite(doubleValue)) {
result = String.valueOf((long) doubleValue);
} else {
result = strValue;
}
} catch (NumberFormatException e) {
// 不是数字,保持原样
result = strValue;
}
}
break;
case NUMERIC:
double numValue = cell.getNumericCellValue();
if (numValue == Math.floor(numValue) && !Double.isInfinite(numValue)) {
result = String.valueOf((long) numValue);
} else {
result = String.valueOf(numValue);
}
break;
case FORMULA:
try {
String formulaValue = cell.getStringCellValue();
result = formulaValue;
} catch (Exception e) {
try {
double numericValue = cell.getNumericCellValue();
if (numericValue == Math.floor(numericValue) && !Double.isInfinite(numericValue)) {
result = String.valueOf((long) numericValue);
} else {
result = String.valueOf(numericValue);
}
} catch (Exception ex) {
// 无法处理返回null
}
}
break;
default:
// 其他类型返回null
}
return result;
}
}