上篇文章讲完了导入的思路,这篇文章就来分享总结一下导出的思路
由于系统很多模块都是单表的导出,且导出的数据要求与分页查询条件查出的数据相匹配,我们这边使用的是easyexcel的api来操作
与导入同理,我们也需要一个导出的VO来接收导出的数据
/**
* @Author xiaolin
* 宿舍管理导出VO
* @Date 2023/5/17 15:04
*/
@Data
public class RoomExVO {
@ExcelProperty(value = "房间号")
private String roomNum;
@ExcelProperty(value = "宿舍类型",converter = DictConvert.class)
@DictFormat("base_room_type")
private String roomType;
@ExcelProperty(value = "具体地址")
private String location;
@ExcelProperty(value = "可容纳数量")
private Integer capacity;
@ExcelProperty(value = "已容纳数量")
private Integer alreadyAccommodated;
// @ExcelProperty(value = "图片url")
// private String photoUrl;
@ExcelProperty(value = "状态")
private String statue;
@ExcelProperty(value = "创建时间")
@DateTimeFormat(FORMAT_YEAR_MONTH_DAY)
private Date createTime;
}
可以看到在导出的接收类里,我们给属性贴上的注解不再是@ApiModelProperty,变成了@ExcelProperty,这是easyexcel导出读操作的一个注解
点击@ExcelProperty
public @interface ExcelProperty {
/**
* The name of the sheet header.
*
* <p>
* write: It automatically merges when you have more than one head
* <p>
* read: When you have multiple heads, take the first one
*
* @return The name of the sheet header
*/
String[] value() default {""};
/**
* Index of column
*
* Read or write it on the index of column,If it's equal to -1, it's sorted by Java class.
*
* priority: index > order > default sort
*
* @return Index of column
*/
int index() default -1;
/**
* Defines the sort order for an column.
*
* priority: index > order > default sort
*
* @return Order of column
*/
int order() default Integer.MAX_VALUE;
/**
* Force the current field to use this converter.
*
* @return Converter
*/
Class<? extends Converter> converter() default AutoConverter.class;
/**
*
* default @see com.alibaba.excel.util.TypeUtil if default is not meet you can set format
*
* @return Format string
* @deprecated please use {@link com.alibaba.excel.annotation.format.DateTimeFormat}
*/
@Deprecated
String format() default "";
}
我们可以看见
Class<? extends Converter> converter() default AutoConverter.class;
这个是easyexcel定义的一个字典转换的一个方法,我们写一个类实现这个方法
public class DictConvert implements Converter<Object> {
@Override
public Class<?> supportJavaTypeKey() {
throw new UnsupportedOperationException("暂不支持,也不需要");
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
throw new UnsupportedOperationException("暂不支持,也不需要");
}
@Override
public Object convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
// 使用字典解析
String type = getType(contentProperty);
String label = cellData.getStringValue();
String value = DictFrameworkUtils.parseDictDataValue(type, label);
if (value == null) {
log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label);
return null;
}
// 将 String 的 value 转换成对应的属性
Class<?> fieldClazz = contentProperty.getField().getType();
return Convert.convert(fieldClazz, value);
}
@Override
public CellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
// 空时,返回空
if (object == null) {
return new CellData<>("");
}
// 使用字典格式化
String type = getType(contentProperty);
//如果是List<String>类型,遍历替换字典值后拼接成String返回
if (object instanceof List<?>){
StringBuilder stringBuilder = new StringBuilder();
for (Object o : (List<?>) object){
String value = String.valueOf(o);
stringBuilder.append(DictFrameworkUtils.getDictDataLabel(type,value)).append(" ");
}
return new CellData<>(stringBuilder.toString());
}
//将object分割
String[] split = String.valueOf(object).split(",");
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < split.length; i++) {
String label = DictFrameworkUtils.getDictDataLabel(type, split[i]);
if (label == null) {
log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, split[i]);
return new CellData<>("");
}
if (i != split.length-1){
stringBuilder.append(label).append(",");
}else {
stringBuilder.append(label);
}
}
// 生成 Excel 小表格
return new CellData<>(stringBuilder.toString());
}
private static String getType(ExcelContentProperty contentProperty) {
return contentProperty.getField().getAnnotation(DictFormat.class).value();
}
}
重写了convertToExcelData()方法,利用反射,拿到导出VO里@DictFormat中的值,由于数据库存放的为字典的键,便可根据这个值找到对应的字典,拿到对应的字典值,且考虑存放数据库的有可能是字符串类型的JSON数组,在进行键值转换的时候也需要判断是否为数组,循环并将其字典转换,拼接成String再将其返回
在根据查询条件拿到list之后,需要做的就是把这些数据写入easyexcel封装的流方法中,再以流的形式发给前端,前端获得流后解析为 文件名.xlsx文件
public class 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) throws IOException {
// 输出 Excel
EasyExcel.write(response.getOutputStream(), head)
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
.sheet(sheetName).doWrite(data);
// 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
}
public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
return EasyExcel.read(file.getInputStream(), head, null)
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
.doReadAllSync();
}
}
至此完