最近有个需求,要导出Excel模板。
这个模板需要具备多级表头以及有些列只能输入特定的字典值。
开发了一个工具类满足需求,记录一下。
一、定义一个注解,通过注解方式配置需要下拉的字段和下拉内容:
import java.lang.annotation.*;
/**
* 标注导出的列为下拉框类型,并为下拉框设置内容
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {
/**
* 固定下拉内容
*/
String[] source() default {};
/**
* 设置下拉框的起始行,默认为第二行
*/
int firstRow() default 1;
/**
* 设置下拉框的结束行,默认为最后一行
*/
int lastRow() default 0x10000;
}
二、通过反射解析注解,将数据动态添加到Class模板当中
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data
@Slf4j
public class ExcelSelectedResolve {
/**
* 下拉内容
*/
private String[] source;
/**
* 设置下拉框的起始行,默认为第二行
*/
private int firstRow;
/**
* 设置下拉框的结束行,默认为最后一行
*/
private int lastRow;
public String[] resolveSelectedSource(ExcelSelected excelSelected) {
if (excelSelected == null) {
return null;
}
// 获取固定下拉框的内容
String[] source = excelSelected.source();
if (source.length > 0) {
return source;
}
return null;
}
}
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.Map;
@Data
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {
private final Map selectedMap;
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 这里可以对cell进行任何操作
Sheet sheet = writeSheetHolder.getSheet();
DataValidationHelper helper = sheet.getDataValidationHelper();
selectedMap.forEach((k, v) -> {
// 设置下拉列表的行: 首行,末行,首列,末列
CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
// 设置下拉列表的值
DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());
// 设置约束
DataValidation validation = helper.createValidation(constraint, rangeList);
// 阻止输入非下拉选项的值
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.setShowErrorBox(true);
validation.setSuppressDropDownArrow(true);
validation.createErrorBox("提示", "请输入下拉选项中的内容");
sheet.addValidationData(validation);
});
}
}
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.*;
@Slf4j
public class EasyExcelUtil {
public static final String member_values = "memberValues";
public static final String source = "source";
/**
* 创建即将导出的sheet页(sheet页中含有带下拉框的列)
* @param head 导出的表头信息和配置
* @param sheetNo sheet索引
* @param sheetName sheet名称
* @param 泛型
* @return sheet页
*/
public static WriteSheet writeSelectedSheet(Class head, Integer sheetNo, String sheetName) {
Map selectedMap = resolveSelectedAnnotation(head);
return EasyExcel.writerSheet(sheetNo, sheetName)
.head(head)
.registerWriteHandler(new SelectedSheetWriteHandler(selectedMap))
.build();
}
/**
* 解析表头类中的下拉注解
* @param head 表头类
* @param 泛型
* @return Map<下拉框列索引, 下拉框内容> map
*/
private static Map resolveSelectedAnnotation(Class head) {
Map selectedMap = new HashMap<>();
Field[] fields = head.getDeclaredFields();
for (int i = 0; i < fields.length; i++){
Field field = fields[i];
// 解析注解信息
ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
if (selected != null) {
ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
String[] source = excelSelectedResolve.resolveSelectedSource(selected);
if (source != null && source.length > 0){
excelSelectedResolve.setSource(source);
excelSelectedResolve.setFirstRow(selected.firstRow());
excelSelectedResolve.setLastRow(selected.lastRow());
if (property != null && property.index() >= 0){
selectedMap.put(property.index(), excelSelectedResolve);
} else {
selectedMap.put(i, excelSelectedResolve);
}
}
}
}
return selectedMap;
}
public static Class dynamicReviseAnnoParam(Class aClass, Map> map) {
Field[] declaredFields = aClass.getDeclaredFields();
try {
for (Field f : declaredFields) {
if (f.isAnnotationPresent(ExcelSelected.class)) {
ExcelSelected annotation = f.getAnnotation(ExcelSelected.class);
InvocationHandler h = Proxy.getInvocationHandler(annotation);
Field hField = h.getClass().getDeclaredField(member_values);
hField.setAccessible(true);
Map memberValues = (Map) hField.get(h);
String name = f.getName();
if (map == null){
map = new HashMap<>();
}
List arr = map.get(name);
if (CollectionUtils.isEmpty(arr)){
continue;
}
String[] array = arr.toArray(new String[arr.size()]);
memberValues.put(source, array);
}
}
} catch (NoSuchFieldException | IllegalAccessException e){
log.error("动态添加注解数据失败!");
e.printStackTrace();
}
return aClass;
}
public static void generatorTemplateBySelected(HttpServletResponse response,String filename,
Map> selectedData,
Class instance){
try {
if (StringUtils.isBlank(filename)){
filename = UUID.randomUUID().toString();
}
filename = new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", String.format("attachment; filename=\"%s\"", filename + ".xlsx"));
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", -1);
response.setCharacterEncoding("UTF-8");
Class classs = EasyExcelUtil.dynamicReviseAnnoParam(instance,selectedData);
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
WriteSheet writeSheet = EasyExcelUtil.writeSelectedSheet(classs, 0, "测试sheet");
excelWriter.write(new ArrayList(), writeSheet);
excelWriter.finish();
}catch (Exception e){
e.printStackTrace();
log.error("导出模板失败!");
}
}
}
三、配置导出模板
@Data
@HeadStyle(fillForegroundColor = 30)
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(10)
public class User {
@ExcelProperty(value = {"编号","编号"})
private Integer id;
@ExcelProperty(value = {"学生档案","姓名"})
@ExcelSelected
private String name;
@ExcelProperty(value = {"学生档案","电话"})
private String phone;
@ExcelProperty(value = {"学生档案","性别"})
@ExcelSelected(firstRow = 2)
private String sex;
@ExcelProperty(value = {"分组","分组"})
@ExcelSelected
private String group;
}
四、通过接口调用工具类导出Excel
@RestController
@Slf4j
public class exportController {
@GetMapping("/exportSelectedExcelDemo")
public void exportDemo(HttpServletRequest request, HttpServletResponse response){
String [] arr = {"男","女"};
String [] group = {"蓝队","绿队"};
HashMap> stringHashMap = new HashMap<>();
stringHashMap.put("sex", Arrays.asList(arr));
stringHashMap.put("group", Arrays.asList(group));
EasyExcelUtil.generatorTemplateBySelected(response,null,stringHashMap,User.class);
}
}
最终导出效果: