1.pom 引入poi
<!-- poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
2.创建excel导出工具类
@Slf4j
public class ExportExcelUtils {
/**
* 导出Excel
*
* @param excelName 要导出的excel名称
* @param list 要导出的数据集合
* @param c 中英文字段对应Map,即要导出的excel表头
* @param response 使用response可以导出到浏览器
* @param <T>
*/
public static <T> void export(String excelName, List<T> list, Class<T> c, HttpServletResponse response) {
// 设置日期格式
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
// 设置默认文件名为当前时间:年月日时分秒
if (excelName == null || excelName == "") {
excelName = df.format(new Date());
} else {
excelName = excelName + df.format(new Date());
}
try {
//创建一个WorkBook,对应一个Excel文件
HSSFWorkbook wb = new HSSFWorkbook();
//在Workbook中,创建一个sheet,对应Excel中的工作薄(sheet)
HSSFSheet sheet = wb.createSheet(excelName);
//设置 边距、页眉、页脚
HSSFPrintSetup printSetup = sheet.getPrintSetup();
//打印方向,true:横向,false:纵向(默认)
printSetup.setLandscape(true);
printSetup.setHeaderMargin(0.2);
printSetup.setFooterMargin(0.2);
//设置打印缩放为88%
///printSetup.setScale((short) 55);
printSetup.setFitHeight((short) 0);
printSetup.setFitWidth((short) 1);
//列从左向右显示②
/// printSetup.setLeftToRight(true);
// 纸张
printSetup.setPaperSize(HSSFPrintSetup.A4_PAPERSIZE);
// 页边距(下)
sheet.setMargin(HSSFSheet.BottomMargin, 0.8);
// 页边距(左)
sheet.setMargin(HSSFSheet.LeftMargin, 0);
// 页边距(右)
sheet.setMargin(HSSFSheet.RightMargin, 0);
// 页边距(上)
sheet.setMargin(HSSFSheet.TopMargin, 0.8);
//设置打印页面为水平居中
sheet.setHorizontallyCenter(true);
sheet.setVerticallyCenter(true);
sheet.setAutobreaks(false);
sheet.setFitToPage(false);
Footer footer = sheet.getFooter();
//设置页数
footer.setCenter("第" + HeaderFooter.page() + "页,共 " + HeaderFooter.numPages() + "页");
Header header = sheet.getHeader();
//自定义页眉,并设置页眉 左中右显示信息
//居中
///header.setCenter("Center Header");
//靠左
header.setLeft(HSSFHeader.font("宋体", "") +
HSSFHeader.fontSize((short) 16) + excelName + ".xlsx");
//靠右
///header.setRight(HSSFHeader.font("Stencil-Normal", "Italic") +
///HSSFHeader.fontSize((short) 16) + "Right w/ Stencil-Normal Italic font and size 16");
//创建单元格,并设置值表头 设置表头居中
HSSFCellStyle style = wb.createCellStyle();
//设置边框
//下边框
style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
//左边框
style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
//上边框
style.setBorderTop(HSSFCellStyle.BORDER_THIN);
//右边框
style.setBorderRight(HSSFCellStyle.BORDER_THIN);
//自动换行
///style.setWrapText(true);
//创建一个居中格式
style.setAlignment(HSSFCellStyle.ALIGN_CENTER);
//上下居中
style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
//设置字体
HSSFFont font = wb.createFont();
font.setFontName("宋体");
style.setFont(font);
// 填充工作表
//获取需要转出的excel表头的map字段
LinkedHashMap<String, String> fieldMap = new LinkedHashMap<>();
//循环注解里面的值 填入Link集合
Field[] declaredFields = c.getDeclaredFields();
//获取属性对应的枚举类
LinkedHashMap<String, Class> converterMap = new LinkedHashMap<>();
for (Field declaredField : declaredFields) {
//获取注解对象
ExportEntityMap declaredAnnotation = declaredField.getDeclaredAnnotation(ExportEntityMap.class);
ExportProperty converterAnnotation = declaredField.getDeclaredAnnotation(ExportProperty.class);
if (declaredAnnotation != null) {
fieldMap.put(declaredAnnotation.EnName(), declaredAnnotation.CnName());
if (converterAnnotation != null) {
converterMap.put(declaredAnnotation.EnName(), converterAnnotation.Converter());
}
}
}
fillSheet(sheet, list, fieldMap, style, converterMap);
// 设置response头信息
response.reset();
try {
log.info("excelName:{}", excelName);
String fileName = URLEncoder.encode(excelName + ".xlsx", "UTF-8");
response.setHeader("Content-disposition", "attachment; filename=" + fileName);
} catch (UnsupportedEncodingException e1) {
log.error("导出excel失败,异常:", e1);
}
//将文件输出
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
response.flushBuffer();
wb.write(outputStream);
outputStream.flush();
outputStream.close();
} catch (Exception e) {
log.error("导出Excel失败,异常:", e);
}
}
/**
* 根据字段名获取字段对象
*
* @param fieldName 字段名
* @param clazz 包含该字段的类
* @return 字段
*/
public static Field getFieldByName(String fieldName, Class<?> clazz) {
// 拿到本类的所有字段
Field[] selfFields = clazz.getDeclaredFields();
// 如果本类中存在该字段,则返回
for (Field field : selfFields) {
//如果本类中存在该字段,则返回
if (field.getName().equals(fieldName)) {
return field;
}
}
// 否则,查看父类中是否存在此字段,如果有则返回
Class<?> superClazz = clazz.getSuperclass();
if (superClazz != null && superClazz != Object.class) {
//递归
return getFieldByName(fieldName, superClazz);
}
// 如果本类和父类都没有,则返回空
return null;
}
/**
* 根据字段名获取字段值
*
* @param fieldName 字段名
* @param o 对象
* @return 字段值
* @throws Exception 异常
*/
public static String getFieldValueByName(String fieldName, Object o)
throws Exception {
String fieldValue = null;
//根据字段名得到字段对象
Field field = getFieldByName(fieldName, o.getClass());
//如果该字段存在,则取出该字段的值
if (field != null) {
//类中的成员变量为private,在类外边使用属性值,故必须进行此操作
field.setAccessible(true);
//获取当前对象中当前Field的value
fieldValue = field.get(o) == null ? "" : field.get(o).toString();
String fieldType = field.getGenericType().toString();
if ("class java.math.BigDecimal".equals(fieldType) || "class java.lang.Double".equals(fieldType)) {
fieldValue = new BigDecimal(fieldValue).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
} else if ("class java.time.LocalDateTime".equals(fieldType) || "class java.time.LocalDate".equals(fieldType)) {
fieldValue = fieldValue.replace("T", " ");
}
} else {
throw new Exception(o.getClass().getSimpleName() + "类不存在字段名 "
+ fieldName);
}
return fieldValue;
}
/**
* 根据带路径或不带路径的属性名获取属性值,即接受简单属性名, 如userName等,又接受带路径的属性名,如student.department.name等
*
* @param fieldNameSequence 带路径的属性名或简单属性名
* @param o 对象
* @return 属性值
* @throws Exception 异常
*/
public static String getFieldValueByNameSequence(String fieldNameSequence,
Object o) throws Exception {
String value = null;
// 将fieldNameSequence进行拆分
String[] attributes = fieldNameSequence.split("\\.");
if (attributes.length == 1) {
value = getFieldValueByName(fieldNameSequence, o);
} else {
// 根据数组中第一个连接属性名获取连接属性对象,如student.department.name
Object fieldObj = getFieldValueByName(attributes[0], o);
//截取除第一个属性名之后的路径
String subFieldNameSequence = fieldNameSequence
.substring(fieldNameSequence.indexOf(".") + 1);
//递归得到最终的属性对象的值
value = getFieldValueByNameSequence(subFieldNameSequence, fieldObj);
}
return value;
}
/**
* 向工作表中填充数据
*
* @param sheet excel的工作表名称
* @param list 数据源
* @param fieldMap 中英文字段对应关系的Map
* @param style 表格中的格式
* @param converterMap 属性对应转化器的Map
* @throws Exception 异常
*/
public static <T> void fillSheet(HSSFSheet sheet, List<T> list,
LinkedHashMap<String, String> fieldMap, HSSFCellStyle style, LinkedHashMap<String, Class> converterMap) throws Exception {
// 定义存放英文字段名和中文字段名的数组
String[] enFields = new String[fieldMap.size()];
String[] cnFields = new String[fieldMap.size()];
// 填充数组
int count = 0;
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
enFields[count] = entry.getKey();
cnFields[count] = entry.getValue();
count++;
}
//存储最大列宽
Map<Integer, Integer> maxWidth = new HashMap<>(16);
HSSFRow row = sheet.createRow(0);
HSSFCell cell = null;
// 填充表头
for (int i = 0; i < cnFields.length; i++) {
cell = row.createCell(i);
cell.setCellValue(cnFields[i]);
cell.setCellStyle(style);
sheet.autoSizeColumn(i);
//设置自适应宽高
maxWidth.put(i, cell.getStringCellValue().getBytes().length * 256 + 200);
}
// 填充内容
for (int index = 0; index < list.size(); index++) {
row = sheet.createRow(index + 1);
// 获取单个对象
T item = list.get(index);
int j = 0;
for (int i = 0; i < enFields.length; i++) {
HSSFCell createCell = row.createCell(j);
String objValue = getFieldValueByNameSequence(enFields[i], item);
//获取属性对应的枚举类
Class<Enum> converterClazz = converterMap.get(enFields[i]);
objValue = convertFieldValue(objValue, converterClazz);
String fieldValue = objValue == null ? "" : objValue;
cell = row.createCell(i);
createCell.setCellValue(fieldValue);
int length = createCell.getStringCellValue().getBytes().length * 256 + 200;
//这里把宽度最大限制到15000
if (length > 15000) {
length = 15000;
}
maxWidth.put(j, Math.max(length, maxWidth.get(j)));
j++;
createCell.setCellStyle(style);
}
}
// 列宽自适应
for (int i = 0; i < cnFields.length; i++) {
sheet.setColumnWidth(i, maxWidth.get(i));
}
}
/**
* 转换属性值
*
* @param objValue :
* @param converterClazz :
* @return java.lang.String
**/
private static <T> String convertFieldValue(String objValue, Class<Enum> converterClazz) throws Exception {
if (ObjectUtil.isNotNull(converterClazz)) {
//获取所有枚举实例
Enum[] enumConstants = converterClazz.getEnumConstants();
//根据方法名获取方法
Method valueMethod = converterClazz.getMethod("getValue");
if (ObjectUtil.isNull(valueMethod)) {
throw new Exception(converterClazz.getSimpleName() + "类找不到getValue()方法");
}
Method descMethod = converterClazz.getMethod("getDescription");
if (ObjectUtil.isNull(descMethod)) {
throw new Exception(converterClazz.getSimpleName() + "类找不到getDescription()方法");
}
for (Enum enumFiled : enumConstants) {
//执行枚举方法获得枚举实例对应的值
Object value = valueMethod.invoke(enumFiled);
Object description = descMethod.invoke(enumFiled);
if (ObjectUtil.equal(objValue, value.toString())) {
objValue = description.toString();
}
}
}
return objValue;
}
}
3.自定义标签Export标签
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExportEntityMap {
String EnName() default "实体属性名称";
String CnName() default "excel标题名称";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExportProperty {
Class<? extends Enum> Converter();
}
枚举
public enum SexEnum {
/**
* 男
*/
MALE(1, "MALE", "男"),
/**
* 女
*/
FEMALE(2, "FEMALE", "女");
private int value;
private String code;
private String description;
SexEnum(int value, String code, String description) {
this.value = value;
this.code = code;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
public String getCode() {
return code;
}
}
4. 实体类引入@ExportEntityMap标签
/**
* 性别
*/
@ExportEntityMap(CnName = "性别", EnName = "sex")
//通过@ExportProperty来转换枚举字段
@ExportProperty(Converter = SexEnum.class)
private Integer sex;
/**
* 年龄
*/
@ExportEntityMap(CnName = "年龄", EnName = "age")
private Integer age;
5.在Controller层中调用ExportExcelUtils方法即可
@ApiOperation("xxxx")
@PostMapping(value = "/xxxx")
public void getExport(HttpServletResponse response, @RequestBody ListRo ro) {
//导出数据
ExportExcelUtils.export("name", list, EntityClass.class, response);
}