前言
在日常开发中,Excel导入导出是一个非常常见的需求。虽然市面上有Apache POI、EasyExcel等优秀的开源库,但在实际使用中,我们经常遇到以下痛点:
常见痛点分析
Excel导入导出痛点
复杂表头处理困难
多级表头支持不足
动态列配置复杂
大数据量性能问题
样式定制不够灵活
数据验证功能弱
合并单元格
表头样式不一致
嵌套表头
分组表头
运行时决定列
条件显示列
内存占用高
导出速度慢
复杂样式配置
主题定制困难
数据类型校验
业务规则验证
基于这些痛点,本文将带你从零开始,实现一个功能完善、易于使用的通用Excel工具类,并封装成团队内部SDK。
一、工具类设计思路
1.1 核心设计原则
- 简单易用:通过注解和配置,最小化使用复杂度
- 功能强大:支持复杂表头、动态列、数据验证等高级功能
- 性能优异:基于流式处理,支持大数据量导入导出
- 扩展性强:支持自定义转换器、验证器、样式等
- 类型安全:基于泛型设计,编译时类型检查
1.2 技术选型
- 核心引擎:Apache POI(成熟稳定)
- 注解驱动:自定义注解简化配置
- 流式处理:SXSSFWorkbook/SXSSFSheet处理大数据量
- 模板引擎:支持Excel模板导出
- 数据验证:内置常用验证规则
1.3 整体架构
Excel工具类SDK
核心引擎层
注解定义层
转换器层
验证器层
样式层
工具层
Excel导入引擎
Excel导出引擎
模板引擎
ExcelField
ExcelSheet
ExcelIgnore
数据转换器接口
内置转换器
自定义转换器
数据验证接口
内置验证器
自定义验证器
样式定义
主题管理
日期工具
数字工具
文件工具
二、核心注解定义
2.1 字段级别注解 @ExcelField
java
package com.example.excel.annotation;
import java.lang.annotation.*;
/**
* Excel字段注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelField {
/**
* 列名(支持多级表头,用"|"分隔)
* 示例:@ExcelField(name = "基本信息|姓名")
*/
String name();
/**
* 列索引(从0开始)
*/
int index() default -1;
/**
* 列宽(单位:字符宽度)
*/
int width() default 15;
/**
* 数据格式
*/
String format() default "";
/**
* 转换器
*/
Class<? extends ExcelConverter> converter() default ExcelConverter.class;
/**
* 验证器
*/
Class<? extends ExcelValidator> validator() default ExcelValidator.class;
/**
* 是否必填
*/
boolean required() default false;
/**
* 必填错误提示
*/
String requiredMessage() default "该字段不能为空";
/**
* 是否导出
*/
boolean export() default true;
/**
* 是否导入
*/
boolean import() default true;
/**
* 下拉选项(用于数据验证)
*/
String[] options() default {};
/**
* 数据类型
*/
DataType dataType() default DataType.STRING;
/**
* 数据类型枚举
*/
enum DataType {
STRING, // 字符串
NUMBER, // 数字
DATE, // 日期
BOOLEAN, // 布尔
FORMULA, // 公式
IMAGE // 图片
}
}
2.2 工作表级别注解 @ExcelSheet
java
package com.example.excel.annotation;
import java.lang.annotation.*;
/**
* Excel工作表注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelSheet {
/**
* 工作表名称
*/
String name() default "Sheet1";
/**
* 起始行(从0开始,默认为0)
*/
int startRow() default 0;
/**
* 标题行数
*/
int titleRows() default 1;
/**
* 是否创建表头
*/
boolean createHeader() default true;
/**
* 是否自动调整列宽
*/
boolean autoSizeColumn() default true;
/**
* 表头样式
*/
String headerStyle() default "HEADER";
/**
* 数据样式
*/
String dataStyle() default "DATA";
/**
* 是否启用数据验证
*/
boolean enableValidation() default true;
}
2.3 忽略注解 @ExcelIgnore
java
package com.example.excel.annotation;
import java.lang.annotation.*;
/**
* Excel忽略注解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelIgnore {
}
三、核心功能实现
3.1 转换器接口与实现
java
package com.example.excel.converter;
/**
* Excel数据转换器接口
*/
public interface ExcelConverter {
/**
* 导出时转换:Java对象 -> Excel单元格值
*/
Object exportValue(Object value);
/**
* 导入时转换:Excel单元格值 -> Java对象
*/
Object importValue(Object value);
}
内置转换器实现:
java
package com.example.excel.converter.impl;
import com.example.excel.converter.ExcelConverter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 日期转换器
*/
public class DateConverter implements ExcelConverter {
private String pattern;
public DateConverter() {
this("yyyy-MM-dd");
}
public DateConverter(String pattern) {
this.pattern = pattern;
}
@Override
public Object exportValue(Object value) {
if (value == null) {
return null;
}
if (value instanceof Date) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
return sdf.format((Date) value);
}
return value.toString();
}
@Override
public Object importValue(Object value) {
if (value == null) {
return null;
}
if (value instanceof Date) {
return value;
}
try {
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
return sdf.parse(value.toString());
} catch (Exception e) {
throw new RuntimeException("日期格式错误,期望格式:" + pattern);
}
}
}
/**
* 枚举转换器
*/
public class EnumConverter implements ExcelConverter {
private Class<? extends Enum> enumClass;
public EnumConverter(Class<? extends Enum> enumClass) {
this.enumClass = enumClass;
}
@Override
public Object exportValue(Object value) {
if (value == null) {
return null;
}
if (value instanceof Enum) {
return value.toString();
}
return value;
}
@Override
public Object importValue(Object value) {
if (value == null) {
return null;
}
try {
return Enum.valueOf(enumClass, value.toString());
} catch (Exception e) {
throw new RuntimeException("枚举值错误:" + value);
}
}
}
/**
* 字典转换器
*/
public class DictConverter implements ExcelConverter {
private DictService dictService;
private String dictType;
public DictConverter(DictService dictService, String dictType) {
this.dictService = dictService;
this.dictType = dictType;
}
@Override
public Object exportValue(Object value) {
if (value == null) {
return null;
}
// 字典值 -> 字典标签
return dictService.getLabel(dictType, value.toString());
}
@Override
public Object importValue(Object value) {
if (value == null) {
return null;
}
// 字典标签 -> 字典值
return dictService.getValue(dictType, value.toString());
}
}
3.2 验证器接口与实现
java
package com.example.excel.validator;
/**
* Excel数据验证器接口
*/
public interface ExcelValidator {
/**
* 验证数据
* @param value 待验证的值
* @param fieldName 字段名
* @param rowIndex 行号
* @return 验证结果
*/
ValidationResult validate(Object value, String fieldName, int rowIndex);
/**
* 验证结果
*/
class ValidationResult {
private boolean valid;
private String message;
public ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
public static ValidationResult success() {
return new ValidationResult(true, null);
}
public static ValidationResult fail(String message) {
return new ValidationResult(false, message);
}
public boolean isValid() {
return valid;
}
public String getMessage() {
return message;
}
}
}
内置验证器实现:
java
package com.example.excel.validator.impl;
import com.example.excel.validator.ExcelValidator;
import java.util.regex.Pattern;
/**
* 手机号验证器
*/
public class PhoneValidator implements ExcelValidator {
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
@Override
public ValidationResult validate(Object value, String fieldName, int rowIndex) {
if (value == null || value.toString().trim().isEmpty()) {
return ValidationResult.success(); // 非空验证由required字段控制
}
String phone = value.toString().trim();
if (!PHONE_PATTERN.matcher(phone).matches()) {
return ValidationResult.fail(
String.format("第%d行,%s格式错误:必须是11位手机号", rowIndex + 1, fieldName)
);
}
return ValidationResult.success();
}
}
/**
* 邮箱验证器
*/
public class EmailValidator implements ExcelValidator {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
@Override
public ValidationResult validate(Object value, String fieldName, int rowIndex) {
if (value == null || value.toString().trim().isEmpty()) {
return ValidationResult.success();
}
String email = value.toString().trim();
if (!EMAIL_PATTERN.matcher(email).matches()) {
return ValidationResult.fail(
String.format("第%d行,%s格式错误:邮箱格式不正确", rowIndex + 1, fieldName)
);
}
return ValidationResult.success();
}
}
/**
* 身份证验证器
*/
public class IdCardValidator implements ExcelValidator {
@Override
public ValidationResult validate(Object value, String fieldName, int rowIndex) {
if (value == null || value.toString().trim().isEmpty()) {
return ValidationResult.success();
}
String idCard = value.toString().trim();
if (!isValidIdCard(idCard)) {
return ValidationResult.fail(
String.format("第%d行,%s格式错误:身份证号格式不正确", rowIndex + 1, fieldName)
);
}
return ValidationResult.success();
}
private boolean isValidIdCard(String idCard) {
// 简化验证,实际应该更严格
return idCard.length() == 18 && idCard.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$");
}
}
/**
* 数值范围验证器
*/
public class NumberRangeValidator implements ExcelValidator {
private double min;
private double max;
public NumberRangeValidator(double min, double max) {
this.min = min;
this.max = max;
}
@Override
public ValidationResult validate(Object value, String fieldName, int rowIndex) {
if (value == null || value.toString().trim().isEmpty()) {
return ValidationResult.success();
}
try {
double number = Double.parseDouble(value.toString());
if (number < min || number > max) {
return ValidationResult.fail(
String.format("第%d行,%s数值超出范围:必须在 %.2f 到 %.2f 之间",
rowIndex + 1, fieldName, min, max)
);
}
} catch (NumberFormatException e) {
return ValidationResult.fail(
String.format("第%d行,%s格式错误:必须是数字", rowIndex + 1, fieldName)
);
}
return ValidationResult.success();
}
}
/**
* 自定义正则验证器
*/
public class RegexValidator implements ExcelValidator {
private Pattern pattern;
private String errorMessage;
public RegexValidator(String regex, String errorMessage) {
this.pattern = Pattern.compile(regex);
this.errorMessage = errorMessage;
}
@Override
public ValidationResult validate(Object value, String fieldName, int rowIndex) {
if (value == null || value.toString().trim().isEmpty()) {
return ValidationResult.success();
}
if (!pattern.matcher(value.toString()).matches()) {
return ValidationResult.fail(
String.format("第%d行,%s格式错误:%s", rowIndex + 1, fieldName, errorMessage)
);
}
return ValidationResult.success();
}
}
3.3 样式管理
java
package com.example.excel.style;
import org.apache.poi.ss.usermodel.*;
/**
* Excel样式管理器
*/
public class ExcelStyleManager {
private Workbook workbook;
public ExcelStyleManager(Workbook workbook) {
this.workbook = workbook;
}
/**
* 获取表头样式
*/
public CellStyle getHeaderStyle() {
CellStyle style = workbook.createCellStyle();
// 字体
Font font = workbook.createFont();
font.setFontName("微软雅黑");
font.setFontHeightInPoints((short) 11);
font.setBold(true);
font.setColor(IndexedColors.WHITE.getIndex());
style.setFont(font);
// 背景色
style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 对齐方式
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
// 自动换行
style.setWrapText(true);
return style;
}
/**
* 获取数据样式
*/
public CellStyle getDataStyle() {
CellStyle style = workbook.createCellStyle();
// 字体
Font font = workbook.createFont();
font.setFontName("微软雅黑");
font.setFontHeightInPoints((short) 10);
style.setFont(font);
// 边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
// 对齐方式
style.setAlignment(HorizontalAlignment.LEFT);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
/**
* 获取数字样式
*/
public CellStyle getNumberStyle() {
CellStyle style = getDataStyle();
style.setAlignment(HorizontalAlignment.RIGHT);
DataFormat format = workbook.createDataFormat();
style.setDataFormat(format.getFormat("#,##0.00"));
return style;
}
/**
* 获取日期样式
*/
public CellStyle getDateStyle() {
CellStyle style = getDataStyle();
style.setAlignment(HorizontalAlignment.CENTER);
DataFormat format = workbook.createDataFormat();
style.setDataFormat(format.getFormat("yyyy-mm-dd"));
return style;
}
/**
* 获取错误样式
*/
public CellStyle getErrorStyle() {
CellStyle style = getDataStyle();
// 字体
Font font = workbook.createFont();
font.setColor(IndexedColors.RED.getIndex());
style.setFont(font);
return style;
}
/**
* 创建自定义样式
*/
public CellStyle createCustomStyle(StyleConfig config) {
CellStyle style = workbook.createCellStyle();
// 字体
if (config.getFontName() != null) {
Font font = workbook.createFont();
font.setFontName(config.getFontName());
font.setFontHeightInPoints(config.getFontSize());
font.setBold(config.isBold());
if (config.getFontColor() != null) {
font.setColor(config.getFontColor());
}
style.setFont(font);
}
// 背景色
if (config.getBackgroundColor() != null) {
style.setFillForegroundColor(config.getBackgroundColor());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
}
// 边框
if (config.getBorderType() != null) {
style.setBorderTop(config.getBorderType());
style.setBorderBottom(config.getBorderType());
style.setBorderLeft(config.getBorderType());
style.setBorderRight(config.getBorderType());
}
// 对齐方式
if (config.getHorizontalAlignment() != null) {
style.setAlignment(config.getHorizontalAlignment());
}
if (config.getVerticalAlignment() != null) {
style.setVerticalAlignment(config.getVerticalAlignment());
}
// 自动换行
style.setWrapText(config.isWrapText());
return style;
}
/**
* 样式配置
*/
public static class StyleConfig {
private String fontName;
private short fontSize = 10;
private boolean bold;
private Short fontColor;
private Short backgroundColor;
private BorderStyle borderType;
private HorizontalAlignment horizontalAlignment;
private VerticalAlignment verticalAlignment;
private boolean wrapText;
// Builder模式
public static StyleConfig builder() {
return new StyleConfig();
}
public StyleConfig fontName(String fontName) {
this.fontName = fontName;
return this;
}
public StyleConfig fontSize(short fontSize) {
this.fontSize = fontSize;
return this;
}
public StyleConfig bold(boolean bold) {
this.bold = bold;
return this;
}
public StyleConfig fontColor(Short fontColor) {
this.fontColor = fontColor;
return this;
}
public StyleConfig backgroundColor(Short backgroundColor) {
this.backgroundColor = backgroundColor;
return this;
}
public StyleConfig borderType(BorderStyle borderType) {
this.borderType = borderType;
return this;
}
public StyleConfig horizontalAlignment(HorizontalAlignment horizontalAlignment) {
this.horizontalAlignment = horizontalAlignment;
return this;
}
public StyleConfig verticalAlignment(VerticalAlignment verticalAlignment) {
this.verticalAlignment = verticalAlignment;
return this;
}
public StyleConfig wrapText(boolean wrapText) {
this.wrapText = wrapText;
return this;
}
// Getters
public String getFontName() { return fontName; }
public short getFontSize() { return fontSize; }
public boolean isBold() { return bold; }
public Short getFontColor() { return fontColor; }
public Short getBackgroundColor() { return backgroundColor; }
public BorderStyle getBorderType() { return borderType; }
public HorizontalAlignment getHorizontalAlignment() { return horizontalAlignment; }
public VerticalAlignment getVerticalAlignment() { return verticalAlignment; }
public boolean isWrapText() { return wrapText; }
}
}
四、核心工具类实现
4.1 Excel导出工具类
java
package com.example.excel.util;
import com.example.excel.annotation.ExcelField;
import com.example.excel.annotation.ExcelIgnore;
import com.example.excel.annotation.ExcelSheet;
import com.example.excel.converter.ExcelConverter;
import com.example.excel.style.ExcelStyleManager;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* Excel导出工具类
*/
public class ExcelExportUtil {
/**
* 导出Excel
* @param dataList 数据列表
* @param outputStream 输出流
* @param <T> 数据类型
*/
public static <T> void export(List<T> dataList, OutputStream outputStream) {
if (dataList == null || dataList.isEmpty()) {
throw new IllegalArgumentException("数据列表不能为空");
}
Class<?> clazz = dataList.get(0).getClass();
ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class);
// 创建工作簿
try (SXSSFWorkbook workbook = new SXSSFWorkbook()) {
// 创建工作表
String sheetName = sheetAnnotation != null ? sheetAnnotation.name() : "Sheet1";
Sheet sheet = workbook.createSheet(sheetName);
// 样式管理器
ExcelStyleManager styleManager = new ExcelStyleManager(workbook);
// 获取字段信息
List<FieldInfo> fieldInfos = getFieldInfos(clazz);
// 创建表头
if (sheetAnnotation == null || sheetAnnotation.createHeader()) {
createHeader(sheet, fieldInfos, styleManager, sheetAnnotation);
}
// 填充数据
fillData(sheet, dataList, fieldInfos, styleManager, sheetAnnotation);
// 自动调整列宽
if (sheetAnnotation == null || sheetAnnotation.autoSizeColumn()) {
autoSizeColumn(sheet, fieldInfos.size());
}
// 写入输出流
workbook.write(outputStream);
} catch (Exception e) {
throw new RuntimeException("导出Excel失败", e);
}
}
/**
* 获取字段信息
*/
private static List<FieldInfo> getFieldInfos(Class<?> clazz) {
List<FieldInfo> fieldInfos = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 跳过忽略的字段
if (field.isAnnotationPresent(ExcelIgnore.class)) {
continue;
}
ExcelField fieldAnnotation = field.getAnnotation(ExcelField.class);
if (fieldAnnotation == null || !fieldAnnotation.export()) {
continue;
}
field.setAccessible(true);
fieldInfos.add(new FieldInfo(field, fieldAnnotation));
}
// 按index排序,未设置index的按字段顺序
fieldInfos.sort((a, b) -> {
int indexA = a.annotation.index();
int indexB = b.annotation.index();
if (indexA >= 0 && indexB >= 0) {
return Integer.compare(indexA, indexB);
} else if (indexA >= 0) {
return -1;
} else if (indexB >= 0) {
return 1;
}
return 0;
});
return fieldInfos;
}
/**
* 创建表头
*/
private static void createHeader(Sheet sheet, List<FieldInfo> fieldInfos,
ExcelStyleManager styleManager, ExcelSheet sheetAnnotation) {
CellStyle headerStyle = styleManager.getHeaderStyle();
// 计算最大层级
int maxLevel = 1;
for (FieldInfo fieldInfo : fieldInfos) {
String name = fieldInfo.annotation.name();
int level = name.split("\\|").length;
if (level > maxLevel) {
maxLevel = level;
}
}
// 创建多级表头
for (int level = 0; level < maxLevel; level++) {
Row row = sheet.createRow(level);
int colIndex = 0;
for (FieldInfo fieldInfo : fieldInfos) {
String[] levels = fieldInfo.annotation.name().split("\\|");
if (level < levels.length) {
Cell cell = row.createCell(colIndex);
cell.setCellValue(levels[level]);
cell.setCellStyle(headerStyle);
// 检查是否需要合并单元格
int rowspan = maxLevel - level - (levels.length - level - 1);
if (rowspan > 1) {
sheet.addMergedRegion(new CellRangeAddress(
level, level + rowspan - 1, colIndex, colIndex
));
}
// 检查是否有子级需要横向合并
if (level < levels.length - 1) {
// 计算该层级有多少个子列
int childCount = countChildren(fieldInfos, colIndex, level, levels.length);
if (childCount > 1) {
sheet.addMergedRegion(new CellRangeAddress(
level, level, colIndex, colIndex + childCount - 1
));
}
}
// 设置列宽
sheet.setColumnWidth(colIndex, fieldInfo.annotation.width() * 256);
colIndex++;
}
}
}
}
/**
* 计算子列数量
*/
private static int countChildren(List<FieldInfo> fieldInfos, int startIndex,
int level, int totalLevels) {
int count = 0;
for (int i = startIndex; i < fieldInfos.size(); i++) {
FieldInfo fieldInfo = fieldInfos.get(i);
String[] levels = fieldInfo.annotation.name().split("\\|");
if (levels.length > level && levels[level].equals(
fieldInfos.get(startIndex).annotation.name().split("\\|")[level])) {
count++;
} else {
break;
}
}
return count;
}
/**
* 填充数据
*/
private static <T> void fillData(Sheet sheet, List<T> dataList, List<FieldInfo> fieldInfos,
ExcelStyleManager styleManager, ExcelSheet sheetAnnotation) {
int startRow = sheetAnnotation != null ? sheetAnnotation.titleRows() : 1;
for (int i = 0; i < dataList.size(); i++) {
T data = dataList.get(i);
Row row = sheet.createRow(startRow + i);
for (int j = 0; j < fieldInfos.size(); j++) {
FieldInfo fieldInfo = fieldInfos.get(j);
Cell cell = row.createCell(j);
try {
Object value = fieldInfo.field.get(data);
// 应用转换器
if (fieldInfo.converter != null) {
value = fieldInfo.converter.exportValue(value);
}
// 设置单元格值
setCellValue(cell, value, fieldInfo.annotation.dataType(), styleManager);
// 设置样式
setCellStyle(cell, fieldInfo.annotation.dataType(), styleManager);
} catch (IllegalAccessException e) {
cell.setCellValue("获取数据失败");
}
}
}
}
/**
* 设置单元格值
*/
private static void setCellValue(Cell cell, Object value, ExcelField.DataType dataType,
ExcelStyleManager styleManager) {
if (value == null) {
return;
}
switch (dataType) {
case STRING:
cell.setCellValue(value.toString());
break;
case NUMBER:
if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else {
cell.setCellValue(Double.parseDouble(value.toString()));
}
break;
case DATE:
if (value instanceof java.util.Date) {
cell.setCellValue((java.util.Date) value);
} else {
cell.setCellValue(value.toString());
}
break;
case BOOLEAN:
if (value instanceof Boolean) {
cell.setCellValue((Boolean) value);
} else {
cell.setCellValue(Boolean.parseBoolean(value.toString()));
}
break;
case FORMULA:
cell.setCellFormula(value.toString());
break;
default:
cell.setCellValue(value.toString());
}
}
/**
* 设置单元格样式
*/
private static void setCellStyle(Cell cell, ExcelField.DataType dataType,
ExcelStyleManager styleManager) {
CellStyle style;
switch (dataType) {
case NUMBER:
style = styleManager.getNumberStyle();
break;
case DATE:
style = styleManager.getDateStyle();
break;
default:
style = styleManager.getDataStyle();
}
cell.setCellStyle(style);
}
/**
* 自动调整列宽
*/
private static void autoSizeColumn(Sheet sheet, int columnCount) {
for (int i = 0; i < columnCount; i++) {
sheet.autoSizeColumn(i);
// 设置最大宽度,防止列过宽
int maxWidth = sheet.getColumnWidth(i);
if (maxWidth > 10000) {
sheet.setColumnWidth(i, 10000);
}
}
}
/**
* 字段信息内部类
*/
private static class FieldInfo {
Field field;
ExcelField annotation;
ExcelConverter converter;
FieldInfo(Field field, ExcelField annotation) {
this.field = field;
this.annotation = annotation;
// 初始化转换器
if (annotation.converter() != ExcelConverter.class) {
try {
this.converter = annotation.converter().newInstance();
} catch (Exception e) {
throw new RuntimeException("初始化转换器失败", e);
}
}
}
}
}
4.2 Excel导入工具类
java
package com.example.excel.util;
import com.example.excel.annotation.ExcelField;
import com.example.excel.annotation.ExcelIgnore;
import com.example.excel.annotation.ExcelSheet;
import com.example.excel.converter.ExcelConverter;
import com.example.excel.validator.ExcelValidator;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* Excel导入工具类
*/
public class ExcelImportUtil {
/**
* 导入Excel
* @param inputStream 输入流
* @param clazz 目标类
* @param <T> 数据类型
* @return 导入结果
*/
public static <T> ImportResult<T> importExcel(InputStream inputStream, Class<T> clazz) {
List<T> dataList = new ArrayList<>();
List<ImportError> errors = new ArrayList<>();
try (Workbook workbook = new XSSFWorkbook(inputStream)) {
Sheet sheet = workbook.getSheetAt(0);
ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class);
int startRow = sheetAnnotation != null ? sheetAnnotation.titleRows() : 1;
int totalRows = sheet.getLastRowNum();
// 获取字段信息
List<FieldInfo> fieldInfos = getFieldInfos(clazz);
// 读取数据
for (int i = startRow; i <= totalRows; i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue;
}
try {
T instance = clazz.newInstance();
// 填充字段值
for (FieldInfo fieldInfo : fieldInfos) {
int colIndex = getFieldColumnIndex(fieldInfos, fieldInfo);
Cell cell = row.getCell(colIndex);
if (cell != null) {
Object value = getCellValue(cell);
// 应用转换器
if (fieldInfo.converter != null) {
value = fieldInfo.converter.importValue(value);
}
// 验证数据
ValidationResult validationResult = validateData(
value, fieldInfo, i, fieldInfo.annotation
);
if (!validationResult.isValid()) {
errors.add(new ImportError(
i + 1,
fieldInfo.annotation.name(),
validationResult.getMessage()
));
continue;
}
// 设置字段值
setFieldValue(instance, fieldInfo.field, value);
} else if (fieldInfo.annotation.required()) {
errors.add(new ImportError(
i + 1,
fieldInfo.annotation.name(),
fieldInfo.annotation.requiredMessage()
));
}
}
dataList.add(instance);
} catch (Exception e) {
errors.add(new ImportError(
i + 1,
"整行",
"解析失败:" + e.getMessage()
));
}
}
return new ImportResult<>(dataList, errors);
} catch (Exception e) {
throw new RuntimeException("导入Excel失败", e);
}
}
/**
* 获取字段信息
*/
private static List<FieldInfo> getFieldInfos(Class<?> clazz) {
List<FieldInfo> fieldInfos = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ExcelIgnore.class)) {
continue;
}
ExcelField fieldAnnotation = field.getAnnotation(ExcelField.class);
if (fieldAnnotation == null || !fieldAnnotation.import()) {
continue;
}
field.setAccessible(true);
fieldInfos.add(new FieldInfo(field, fieldAnnotation));
}
// 按index排序
fieldInfos.sort((a, b) -> {
int indexA = a.annotation.index();
int indexB = b.annotation.index();
if (indexA >= 0 && indexB >= 0) {
return Integer.compare(indexA, indexB);
} else if (indexA >= 0) {
return -1;
} else if (indexB >= 0) {
return 1;
}
return 0;
});
return fieldInfos;
}
/**
* 获取字段列索引
*/
private static int getFieldColumnIndex(List<FieldInfo> fieldInfos, FieldInfo target) {
for (int i = 0; i < fieldInfos.size(); i++) {
if (fieldInfos.get(i) == target) {
return i;
}
}
return -1;
}
/**
* 获取单元格值
*/
private static Object getCellValue(Cell cell) {
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
}
return cell.getNumericCellValue();
case BOOLEAN:
return cell.getBooleanCellValue();
case FORMULA:
return cell.getCellFormula();
case BLANK:
return null;
default:
return null;
}
}
/**
* 验证数据
*/
private static ValidationResult validateData(Object value, FieldInfo fieldInfo,
int rowIndex, ExcelField annotation) {
// 必填验证
if (annotation.required() && (value == null || value.toString().trim().isEmpty())) {
return ValidationResult.fail(annotation.requiredMessage());
}
// 应用验证器
if (fieldInfo.validator != null) {
return fieldInfo.validator.validate(value, annotation.name(), rowIndex);
}
return ValidationResult.success();
}
/**
* 设置字段值
*/
private static void setFieldValue(Object instance, Field field, Object value)
throws IllegalAccessException {
if (value == null) {
return;
}
Class<?> fieldType = field.getType();
// 类型转换
if (fieldType == String.class) {
field.set(instance, value.toString());
} else if (fieldType == Integer.class || fieldType == int.class) {
if (value instanceof Number) {
field.set(instance, ((Number) value).intValue());
} else {
field.set(instance, Integer.parseInt(value.toString()));
}
} else if (fieldType == Long.class || fieldType == long.class) {
if (value instanceof Number) {
field.set(instance, ((Number) value).longValue());
} else {
field.set(instance, Long.parseLong(value.toString()));
}
} else if (fieldType == Double.class || fieldType == double.class) {
if (value instanceof Number) {
field.set(instance, ((Number) value).doubleValue());
} else {
field.set(instance, Double.parseDouble(value.toString()));
}
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
if (value instanceof Boolean) {
field.set(instance, value);
} else {
field.set(instance, Boolean.parseBoolean(value.toString()));
}
} else if (fieldType == java.util.Date.class) {
if (value instanceof java.util.Date) {
field.set(instance, value);
} else {
// 需要根据format转换
throw new RuntimeException("日期类型需要配置转换器");
}
} else {
field.set(instance, value);
}
}
/**
* 字段信息内部类
*/
private static class FieldInfo {
Field field;
ExcelField annotation;
ExcelConverter converter;
ExcelValidator validator;
FieldInfo(Field field, ExcelField annotation) {
this.field = field;
this.annotation = annotation;
// 初始化转换器
if (annotation.converter() != ExcelConverter.class) {
try {
this.converter = annotation.converter().newInstance();
} catch (Exception e) {
throw new RuntimeException("初始化转换器失败", e);
}
}
// 初始化验证器
if (annotation.validator() != ExcelValidator.class) {
try {
this.validator = annotation.validator().newInstance();
} catch (Exception e) {
throw new RuntimeException("初始化验证器失败", e);
}
}
}
}
/**
* 导入结果
*/
public static class ImportResult<T> {
private List<T> dataList;
private List<ImportError> errors;
public ImportResult(List<T> dataList, List<ImportError> errors) {
this.dataList = dataList;
this.errors = errors;
}
public List<T> getDataList() {
return dataList;
}
public List<ImportError> getErrors() {
return errors;
}
public boolean hasErrors() {
return !errors.isEmpty();
}
}
/**
* 导入错误
*/
public static class ImportError {
private int rowIndex;
private String fieldName;
private String message;
public ImportError(int rowIndex, String fieldName, String message) {
this.rowIndex = rowIndex;
this.fieldName = fieldName;
this.message = message;
}
public int getRowIndex() {
return rowIndex;
}
public String getFieldName() {
return fieldName;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return String.format("第%d行,%s:%s", rowIndex, fieldName, message);
}
}
/**
* 验证结果
*/
private static class ValidationResult {
private boolean valid;
private String message;
public ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
public static ValidationResult success() {
return new ValidationResult(true, null);
}
public static ValidationResult fail(String message) {
return new ValidationResult(false, message);
}
public boolean isValid() {
return valid;
}
public String getMessage() {
return message;
}
}
}
五、使用示例
5.1 定义实体类
java
package com.example.entity;
import com.example.excel.annotation.*;
import com.example.excel.converter.impl.*;
import com.example.excel.validator.impl.*;
import lombok.Data;
import java.util.Date;
/**
* 用户实体
*/
@Data
@ExcelSheet(name = "用户信息", titleRows = 2, autoSizeColumn = true)
public class UserExcel {
@ExcelField(name = "基本信息|工号", index = 0, width = 15)
private String employeeNo;
@ExcelField(
name = "基本信息|姓名",
index = 1,
width = 12,
required = true,
requiredMessage = "姓名不能为空"
)
private String name;
@ExcelField(
name = "基本信息|性别",
index = 2,
width = 8,
options = {"男", "女"}
)
private String gender;
@ExcelField(
name = "基本信息|年龄",
index = 3,
width = 8,
dataType = ExcelField.DataType.NUMBER
)
private Integer age;
@ExcelField(
name = "联系方式|手机号",
index = 4,
width = 15,
validator = PhoneValidator.class
)
private String phone;
@ExcelField(
name = "联系方式|邮箱",
index = 5,
width = 20,
validator = EmailValidator.class
)
private String email;
@ExcelField(
name = "工作信息|部门",
index = 6,
width = 15
)
private String department;
@ExcelField(
name = "工作信息|职位",
index = 7,
width = 15
)
private String position;
@ExcelField(
name = "工作信息|入职日期",
index = 8,
width = 15,
dataType = ExcelField.DataType.DATE,
converter = DateConverter.class,
format = "yyyy-MM-dd"
)
private Date entryDate;
@ExcelField(
name = "工作信息|薪资",
index = 9,
width = 12,
dataType = ExcelField.DataType.NUMBER,
converter = DateConverter.class
)
private Double salary;
@ExcelField(
name = "其他|状态",
index = 10,
width = 10,
converter = StatusConverter.class
)
private UserStatus status;
@ExcelField(
name = "其他|备注",
index = 11,
width = 30
)
private String remark;
// 忽略导入导出的字段
@ExcelIgnore
private String password;
/**
* 用户状态枚举
*/
public enum UserStatus {
ACTIVE("在职"),
INACTIVE("离职"),
SUSPENDED("停职");
private String desc;
UserStatus(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
/**
* 状态转换器
*/
public static class StatusConverter implements ExcelConverter {
@Override
public Object exportValue(Object value) {
if (value == null) {
return null;
}
return ((UserStatus) value).getDesc();
}
@Override
public Object importValue(Object value) {
if (value == null) {
return null;
}
String desc = value.toString();
for (UserStatus status : UserStatus.values()) {
if (status.getDesc().equals(desc)) {
return status;
}
}
throw new RuntimeException("无效的状态值:" + desc);
}
}
}
5.2 导出示例
java
package com.example.controller;
import com.example.entity.UserExcel;
import com.example.excel.util.ExcelExportUtil;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@RestController
@RequestMapping("/api/excel")
public class ExcelExportController {
/**
* 导出用户数据
*/
@GetMapping("/export/users")
public void exportUsers(HttpServletResponse response) throws IOException {
// 准备数据
List<UserExcel> userList = new ArrayList<>();
UserExcel user1 = new UserExcel();
user1.setEmployeeNo("E001");
user1.setName("张三");
user1.setGender("男");
user1.setAge(28);
user1.setPhone("13800138000");
user1.setEmail("zhangsan@example.com");
user1.setDepartment("技术部");
user1.setPosition("Java开发工程师");
user1.setEntryDate(new Date());
user1.setSalary(15000.0);
user1.setStatus(UserExcel.UserStatus.ACTIVE);
user1.setRemark("优秀员工");
userList.add(user1);
UserExcel user2 = new UserExcel();
user2.setEmployeeNo("E002");
user2.setName("李四");
user2.setGender("女");
user2.setAge(26);
user2.setPhone("13900139000");
user2.setEmail("lisi@example.com");
user2.setDepartment("产品部");
user2.setPosition("产品经理");
user2.setEntryDate(new Date());
user2.setSalary(18000.0);
user2.setStatus(UserExcel.UserStatus.ACTIVE);
user2.setRemark("工作认真");
userList.add(user2);
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = "用户信息_" + System.currentTimeMillis() + ".xlsx";
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
// 导出Excel
ExcelExportUtil.export(userList, response.getOutputStream());
}
}
5.3 导入示例
java
package com.example.controller;
import com.example.entity.UserExcel;
import com.example.excel.util.ExcelImportUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/excel")
public class ExcelImportController {
/**
* 导入用户数据
*/
@PostMapping("/import/users")
public Map<String, Object> importUsers(@RequestParam("file") MultipartFile file) {
Map<String, Object> result = new HashMap<>();
try {
// 导入Excel
ExcelImportUtil.ImportResult<UserExcel> importResult =
ExcelImportUtil.importExcel(file.getInputStream(), UserExcel.class);
// 检查是否有错误
if (importResult.hasErrors()) {
result.put("success", false);
result.put("message", "导入失败,存在错误数据");
result.put("errors", importResult.getErrors());
return result;
}
// 保存数据
List<UserExcel> userList = importResult.getDataList();
userService.batchSave(userList);
result.put("success", true);
result.put("message", "导入成功,共导入 " + userList.size() + " 条数据");
result.put("data", userList);
} catch (IOException e) {
result.put("success", false);
result.put("message", "文件读取失败:" + e.getMessage());
} catch (Exception e) {
result.put("success", false);
result.put("message", "导入失败:" + e.getMessage());
}
return result;
}
}
六、高级功能
6.1 动态列配置
java
package com.example.excel.config;
import com.example.excel.annotation.ExcelField;
import lombok.Data;
/**
* 动态列配置
*/
@Data
public class DynamicColumnConfig {
/**
* 列名
*/
private String name;
/**
* 字段名
*/
private String fieldName;
/**
* 列宽
*/
private int width = 15;
/**
* 数据类型
*/
private ExcelField.DataType dataType = ExcelField.DataType.STRING;
/**
* 格式
*/
private String format;
/**
* 是否显示
*/
private boolean visible = true;
/**
* 排序
*/
private int order;
}
6.2 模板导出
java
package com.example.excel.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
/**
* Excel模板导出工具
*/
public class ExcelTemplateUtil {
/**
* 基于模板导出
* @param templateStream 模板文件流
* @param dataMap 数据映射
* @param outputStream 输出流
*/
public static void exportByTemplate(InputStream templateStream,
Map<String, Object> dataMap,
OutputStream outputStream) {
try (Workbook workbook = new XSSFWorkbook(templateStream)) {
Sheet sheet = workbook.getSheetAt(0);
// 替换模板中的占位符
replacePlaceholders(sheet, dataMap);
// 写入输出流
workbook.write(outputStream);
} catch (Exception e) {
throw new RuntimeException("模板导出失败", e);
}
}
/**
* 替换占位符
*/
private static void replacePlaceholders(Sheet sheet, Map<String, Object> dataMap) {
for (Row row : sheet) {
for (Cell cell : row) {
if (cell.getCellType() == CellType.STRING) {
String value = cell.getStringCellValue();
if (value != null && value.startsWith("${") && value.endsWith("}")) {
String key = value.substring(2, value.length() - 1);
Object data = dataMap.get(key);
if (data != null) {
cell.setCellValue(data.toString());
}
}
}
}
}
}
}
6.3 数据验证配置
java
package com.example.excel.validation;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
/**
* Excel数据验证工具
*/
public class ExcelDataValidationUtil {
/**
* 添加下拉验证
* @param sheet 工作表
* @param firstRow 起始行
* @param lastRow 结束行
* @param firstCol 起始列
* @param lastCol 结束列
* @param options 选项列表
*/
public static void addDropdownValidation(Sheet sheet, int firstRow, int lastRow,
int firstCol, int lastCol, String[] options) {
DataValidationHelper validationHelper = sheet.getDataValidationHelper();
// 创建下拉列表
DataValidationConstraint constraint = validationHelper.createExplicitListConstraint(options);
// 设置验证区域
CellRangeAddressList addressList = new CellRangeAddressList(
firstRow, lastRow, firstCol, lastCol
);
// 创建数据验证
DataValidation validation = validationHelper.createValidation(constraint, addressList);
// 设置错误提示
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.createErrorBox("输入错误", "请从下拉列表中选择");
// 添加到工作表
sheet.addValidationData(validation);
}
/**
* 添加数字范围验证
* @param sheet 工作表
* @param firstRow 起始行
* @param lastRow 结束行
* @param firstCol 起始列
* @param lastCol 结束列
* @param min 最小值
* @param max 最大值
*/
public static void addNumberRangeValidation(Sheet sheet, int firstRow, int lastRow,
int firstCol, int lastCol, double min, double max) {
DataValidationHelper validationHelper = sheet.getDataValidationHelper();
// 创建数字约束
DataValidationConstraint constraint = validationHelper.createNumericConstraint(
DataValidationConstraint.ValidationType.DECIMAL,
DataValidationConstraint.OperatorType.BETWEEN,
String.valueOf(min),
String.valueOf(max)
);
// 设置验证区域
CellRangeAddressList addressList = new CellRangeAddressList(
firstRow, lastRow, firstCol, lastCol
);
// 创建数据验证
DataValidation validation = validationHelper.createValidation(constraint, addressList);
// 设置错误提示
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.createErrorBox("输入错误",
String.format("请输入 %.2f 到 %.2f 之间的数字", min, max));
// 添加到工作表
sheet.addValidationData(validation);
}
}
七、性能优化
7.1 大数据量处理
java
package com.example.excel.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.OutputStream;
import java.util.List;
/**
* 大数据量Excel导出工具
*/
public class BigDataExcelExportUtil {
/**
* 分批导出大数据量
* @param dataProvider 数据提供者
* @param outputStream 输出流
* @param batchSize 批次大小
*/
public static <T> void exportBatch(DataProvider<T> dataProvider,
OutputStream outputStream,
int batchSize) {
try (SXSSFWorkbook workbook = new SXSSFWorkbook(1000)) { // 保留1000行在内存中
Sheet sheet = workbook.createSheet("数据");
int rowNum = 0;
List<T> batch;
while ((batch = dataProvider.getNextBatch(batchSize)) != null && !batch.isEmpty()) {
for (T data : batch) {
Row row = sheet.createRow(rowNum++);
// 填充数据
fillRow(row, data);
}
// 定期刷新到磁盘
if (rowNum % 1000 == 0) {
((SXSSFSheet) sheet).flushRows();
}
}
workbook.write(outputStream);
} catch (Exception e) {
throw new RuntimeException("大数据量导出失败", e);
}
}
/**
* 数据提供者接口
*/
public interface DataProvider<T> {
/**
* 获取下一批数据
*/
List<T> getNextBatch(int batchSize);
}
private static <T> void fillRow(Row row, T data) {
// 实现数据填充逻辑
}
}
7.2 并行处理
java
package com.example.excel.util;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* 并行Excel处理工具
*/
public class ParallelExcelUtil {
private static final ExecutorService executor =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
/**
* 并行处理数据
* @param dataList 数据列表
* @param processor 处理器
* @return 处理结果列表
*/
public static <T, R> List<R> processParallel(List<T> dataList,
Processor<T, R> processor) {
List<CompletableFuture<R>> futures = dataList.stream()
.map(data -> CompletableFuture.supplyAsync(
() -> processor.process(data),
executor
))
.collect(Collectors.toList());
return futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
/**
* 处理器接口
*/
public interface Processor<T, R> {
R process(T data);
}
/**
* 关闭线程池
*/
public static void shutdown() {
executor.shutdown();
}
}
八、最佳实践与注意事项
8.1 使用建议
- 注解优先:优先使用注解配置,减少代码侵入性
- 合理分批:大数据量导入导出时,使用分批处理
- 异常处理:完善的异常处理和错误提示
- 性能监控:监控导入导出性能,及时优化
- 资源管理:使用try-with-resources确保资源释放
8.2 常见问题
Q1:如何处理复杂的嵌套对象?
A:使用转换器在导入导出时进行对象转换,或者在实体类中增加扁平化字段。
Q2:如何处理动态列?
A:使用DynamicColumnConfig配置动态列,或者使用模板导出方式。
Q3:大数据量导出内存占用过高?
A:使用SXSSFWorkbook的流式处理,设置合适的内存缓存行数。
Q4:如何自定义样式?
A:通过ExcelStyleManager创建自定义样式,或者在注解中指定样式配置。
8.3 团队规范建议
- 统一注解规范:团队内统一使用相同的注解配置
- 命名规范:导出文件名包含业务和时间戳
- 数据验证:所有导入数据必须经过验证
- 日志记录:记录导入导出的关键操作和错误
- 版本管理:Excel模板版本化管理
九、总结
本文实现了一个功能完善的通用Excel导入导出工具类,核心功能:
- ✅ 注解驱动,使用简单
- ✅ 支持复杂多级表头
- ✅ 支持动态列配置
- ✅ 内置常用转换器和验证器
- ✅ 支持自定义转换器和验证器
- ✅ 完善的错误提示机制
- ✅ 大数据量性能优化
- ✅ 模板导出支持
- ✅ 数据验证支持