基于 EasyExcel实现用户自定义导出excel顺序
pom 文件
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
实体类
@Data
public class Student {
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "名字")
private String name;
@ExcelProperty(value = "年龄")
private String age;
}
控制层
@PostMapping("/export")
public void export(HttpServletResponse response, @RequestBody Student stu) {
//用户选择要导出的循序,这里模拟前端传递
List<String> includeColumnFiledNames = new ArrayList<>();
includeColumnFiledNames.add("name");
includeColumnFiledNames.add("age");
String fileName = "学生一览"
//查询数据库
List<Student> data = studentSereice.getstuList(stu);
//导出
ExcelUtils.write(response, fileName + ".xlsx", fileName, Student.class, data, includeColumnFiledNames);
}
工具类
public class ExcelUtils {
private ExcelUtils() {
}
/**
* 将列表以 Excel 响应给前端
* @param response 响应
* @param fileName 文件名
* @param sheetName Excel sheet 名
* @param head Excel head 头
* @param data 数据列表哦
* @param <T> 泛型,保证 head 和 data 类型的一致性
* @throws IOException 写入失败的情况
*/
public static <T> void write(HttpServletResponse response, String fileName, String sheetName, Class<T> head, List<T> data,
List<String> includeColumnFiledNames) throws Exception {
//设置响应头,指定为Excel文件下载
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
EasyExcelFactory.write(response.getOutputStream(), head).
// 只要导出对象中的字段的数据
includeColumnFieldNames(includeColumnFiledNames)
//会根据传入集合的顺序排序
.orderByIncludeColumn(true)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet(sheetName)
.doWrite(data);
}
}
3.3版本以下的坑
orderByIncludeColumn 方法是3.3新增方法,includeColumnFieldNames方法只是筛选并不负责排序,所以正常思路是根据前端传递的includeColumnFiledNames 参数动态设置导出实体类的属性ExcelProperty注解的index值.如下:
private static <T> void handelHead(Class<T> head, List<String> includeColumnFiledNames) throws NoSuchFieldException, IllegalAccessException {
for (String filedName :includeColumnFiledNames){
Field field = head.getDeclaredField(filedName);
ExcelProperty excelPropertyAnnotation = field.getAnnotation(ExcelProperty.class);
InvocationHandler h = Proxy.getInvocationHandler(excelPropertyAnnotation);
Field indexField = h.getClass().getDeclaredField("memberValues");
// 因为这个字段事 private final 修饰,所以要打开权限
indexField.setAccessible(true);
// 获取 memberValues
Map<String,Object> memberValues = (Map<String, Object>) indexField.get(h);
// 修改 value 属性值
memberValues.put("index", includeColumnFiledNames.indexOf(filedName));
}
}
以上代码第一次运行是没问题的,但用户如果重新选择新的顺序就会出第一次运行的顺序.看了官方文档才知道解析class的field会有缓存,以前全局放到Map里面,3.3.0 以后默认放到ThreadLocal里了.所以建议使用3.3版本,或不使用实体类导出,使用List<Map<String,Object>>