基于阿里的easyexcel的通用的导入导出方案
ExcelUploadResult
/**
* excel文件上传的结果
*/
@Data
public class ExcelUploadResult {
/**
* 总行数
*/
private int total;
/**
* 成功的数量
*/
private int successTotal;
/**
* 失败的数据集
*/
private List errorRows = new ArrayList<>();
/**
* 转化为String
*/
public final String convert() {
StringBuilder result = new StringBuilder();
int total = this.getTotal();
int successTotal = this.getSuccessTotal();
result.append(I18nUtil.getMessage("total") + ": " + total + ", ");
result.append(I18nUtil.getMessage("success_total") + ": " + successTotal + ";");
List errorRows = this.getErrorRows();
for (ExcelErrorRow errorRow : errorRows) {
result.append("\n");
Integer num = errorRow.getNum();
result.append(I18nUtil.getMessage("row") + num + ", ");
String reason = errorRow.getReason();
result.append(I18nUtil.getMessage("fail_reason") + ": " + reason);
}
return result.toString();
}
}
ElTableColumn
@Data
public class ElTableColumn implements Serializable ,Comparable {
private static final long serialVersionUID = -8735750649377906399L;
/**
* 展示序号,在第一列为0,第二列为1,以此类推
*/
private Integer index;
/**
* 字段名称 默认取实体类中对应的字段名
*/
private String field;
/**
* 中英文展示名称
*/
private String label;
/**
* 表格宽度
*/
private int width;
/**
* 对齐方式 left/right/center
*/
private String align;
/**
* 自定义formatter方法
*/
private String formatter;
/**
* 列是否固定在左侧或者右侧,true 表示固定在左侧 left, right
*/
private String fixed;
/**
* 是否显示,true-默认显示
*/
private Boolean display;
/**
* 是否导出,true-默认导出
*/
private Boolean export;
/**
* 是否为模板字段,true-默认导出
*/
private Boolean template;
/**
* 是否可为空,true-导入校验用
*/
private Boolean empty;
/**
* 长度,100-导入校验用
*/
private Integer length;
/**
* 正则表达式-导入校验用
*/
private String reg;
/**
* 错误描述,导入校验用
*/
private String errorMsg;
/**
* 校验数据用
*/
private String cacheKey;
@Override
public int compareTo(ElTableColumn anotherColumn) {
return index - anotherColumn.getIndex();
}
@Override
public String toString() {
return "ElTableColumn{" +
"index=" + index +
", prop='" + field + '\'' +
", label='" + label + '\'' +
", width=" + width +
", align='" + align + '\'' +
", formatter='" + formatter + '\'' +
", fixed='" + fixed + '\'' +
", display=" + display +
", export=" + export +
", template=" + template +
", empty=" + empty +
", length=" + length +
", reg='" + reg + '\'' +
", errorMsg='" + errorMsg + '\'' +
", cacheKey='" + cacheKey + '\'' +
'}';
}
}
ExcelErrorRow
@Data
public class ExcelErrorRow {
/**
* 行数
*/
private Integer num;
/**
* 失败原因
*/
private String reason;
public ExcelErrorRow() {
}
public ExcelErrorRow(int num, String reason) {
this.num = num;
this.reason = reason;
}
public ExcelErrorRow(int num) {
this.num = num;
}
}
UploadDataListener
@Slf4j
public class UploadDataListener extends AnalysisEventListener {
/**
* excel 上传结果
*/
private ExcelUploadResult excelUploadResult = new ExcelUploadResult();
/**
* 额外参数
*/
private Map params;
/**
* 默认值:true
* true:忽略错误的记录,导入正确的记录
* false:若出现一条错误记录,则都不导入
*/
private boolean flag = true;
public ExcelUploadResult getExcelUploadResult() {
return excelUploadResult;
}
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_SIZE = 10000;
List list = new ArrayList();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private IBaseService baseService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param baseService
*/
public UploadDataListener(IBaseService baseService) {
this.baseService = baseService;
}
public UploadDataListener(IBaseService baseService, Map params) {
this.baseService = baseService;
this.params = params;
}
/**
* 这个每一条数据解析都会来调用
*
* @param t one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(T t, AnalysisContext context) {
// 设置总条数
excelUploadResult.setTotal(excelUploadResult.getTotal() + 1);
Integer rowIndex = context.readRowHolder().getRowIndex();
boolean bool = baseService.checkExcelRow(t, excelUploadResult.getErrorRows(), rowIndex + 1, params);
if (bool) {
// 设置成功总条数
excelUploadResult.setSuccessTotal(excelUploadResult.getSuccessTotal() + 1);
list.add(t);
}
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_SIZE) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
@Transactional(rollbackFor = Exception.class)
public void saveData() {
log.info("{}条数据,开始存储数据库!", list.size());
try {
if (CollectionUtils.isNotEmpty(list)) {
flag = baseService.filterData(list, excelUploadResult.getErrorRows(),excelUploadResult);
if (flag) {
List data = baseService.convertData(this.list);
baseService.saveBatch(data, BATCH_SIZE);
}
}
} catch (Exception e) {
excelUploadResult.setSuccessTotal(excelUploadResult.getSuccessTotal() - list.size());
log.error("导入excel保存数据库出错", e);
}
log.info("存储数据库成功!");
}
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
excelUploadResult.setTotal(excelUploadResult.getTotal() + 1);
List errorRows = excelUploadResult.getErrorRows();
ExcelErrorRow errorRow = new ExcelErrorRow();
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
log.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex());
errorRow.setNum(excelDataConvertException.getRowIndex() + 1);
errorRow.setReason("第" + (excelDataConvertException.getColumnIndex() + 1) + "列解析异常");
} else {
errorRow.setNum(-1);
errorRow.setReason("解析失败,请检查数据格式");
log.error("解析失败", exception);
}
errorRows.add(errorRow);
}
}
TableColumn
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TableColumn {
/**
* 展示序号,在第一列为0,第二列为1,以此类推
*/
int index() default 0;
/**
* 中英文展示名称
*/
String label() default "";
/**
* 表格宽度
*/
int width() default 150;
/**
* 对齐方式 left/right/center
*/
String align() default CENTER;
/**
* 自定义formatter方法
*/
String formatter() default "";
/**
* 列是否固定在左侧或者右侧,true 表示固定在左侧 left, right
*/
String fixed() default "true";
/**
* 居中对齐
*/
String CENTER = "center";
/**
* 左对齐
*/
String LEFT = "left";
/**
* 右对齐
*/
String RIGHT = "right";
/**
* 是否显示,true-默认显示
*/
boolean display() default true;
/**
* 是否导出,true-默认导出
*/
boolean export() default true;
/**
* 是否为模板字段,true-默认导出
*/
boolean template() default true;
/**
* 校验数据用
* key值对应的数据是否在缓存存在
*/
String cacheKey() default "";
/**
* 是否可为空,true-导入校验用
*/
boolean empty() default true;
/**
* 长度,100-导入校验用
*/
int length() default 100;
/**
* 正则表达式-导入校验用
*/
String reg() default "";
/**
* 错误描述,导入校验用
*/
String errorMsg() default "";
}
BaseServiceImpl
/**
* @Description: ServiceImpl基类
*/
@Slf4j
public class BaseServiceImpl, T> extends ServiceImpl implements IBaseService {
@Override
public QueryWrapper buildQueryWrapper(T t) {
QueryWrapper queryWrapper = new QueryWrapper<>(t);
queryWrapper.orderByDesc("id");
return queryWrapper;
}
@Override
public void exportXlsTemplate(HttpServletResponse response, Class clazz, String fileName, List list) {
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
List elTableColumns = ElTableColumnUtil.getAllTableColumns(clazz);
List> dataList = new ArrayList<>();
dataList.add(list);
if (CollectionUtils.isNotEmpty(elTableColumns)) {
List> heads = getHeads(elTableColumns, true);
EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("sheet1").head(heads).doWrite(dataList);
} else {
EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("sheet1").doWrite(dataList);
}
} catch (Exception e) {
log.error("导出Excel失败", e);
}
}
@Override
public void exportXlsTemplate(HttpServletResponse response, Class clazz, String fileName) {
this.exportXls(response, clazz, fileName, new ArrayList<>(), true);
}
@Override
public void exportXls(HttpServletResponse response, Class clazz, String fileName, List list) {
this.exportXls(response, clazz, fileName, list, false);
}
@Override
public void exportDataByXls(HttpServletResponse response, Class clazz, String fileName, List list) {
this.exportXls(response, clazz, fileName, list, false);
}
private void exportXls(HttpServletResponse response, Class clazz, String fileName, List list, boolean isTemplate) {
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
List elTableColumns = ElTableColumnUtil.getAllTableColumns(clazz);
if (CollectionUtils.isNotEmpty(elTableColumns)) {
List> heads = getHeads(elTableColumns, isTemplate);
List> dataList = new ArrayList<>();
if (!isTemplate) {
dataList = getDataList(elTableColumns, list);
}
EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("sheet1").head(heads).doWrite(dataList);
} else {
EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("sheet1").doWrite(list);
}
} catch (Exception e) {
log.error("导出Excel失败", e);
}
}
@Override
public ExcelUploadResult importXls(MultipartFile file, Class clazz) {
return importXls(file, clazz, new HashMap<>());
}
@Override
public ExcelUploadResult importXls(MultipartFile file, Class clazz, Map params) {
UploadDataListener uploadDataListener = new UploadDataListener(this, params);
try {
EasyExcel.read(file.getInputStream(), clazz, uploadDataListener).sheet().doRead();
} catch (Exception e) {
log.error("导入Excel失败", e);
}
ExcelUploadResult excelUploadResult = uploadDataListener.getExcelUploadResult();
return excelUploadResult;
}
@Override
public ExcelUploadResult syncImportXls(MultipartFile file, Class clazz, Map params) {
//excel 上传结果
ExcelUploadResult excelUploadResult = new ExcelUploadResult();
List errorRows = excelUploadResult.getErrorRows();
try {
List list = EasyExcel.read(file.getInputStream()).head(clazz).sheet().doReadSync();
for (int i = 0; i < list.size(); i++) {
T record = list.get(i);
checkExcelRow(record, errorRows, i + 1, params);
}
this.saveBatch(list);
} catch (Exception e) {
log.error("导入Excel失败", e);
}
return excelUploadResult;
}
@Override
public boolean checkExcelRow(T record, List errorRows, int rowNum, Map params) {
try {
Class clazz = record.getClass();
final Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//打开私有访问
field.setAccessible(true);
//获取属性值
Object value = field.get(record);
if (value != null) {
if (value instanceof String) {
String valStr = (String) value;
if (!valStr.trim().isEmpty()) {
return true;
}
} else {
return true;
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
/**
* 获取需要导出的字段,以便生成表头
*/
protected List> getHeads(List elTableColumns, boolean isTemplate) {
List> heads = new ArrayList<>();
if (CollectionUtils.isNotEmpty(elTableColumns)) {
heads = elTableColumns.stream().filter(column -> {
if (isTemplate) {
if (column.getTemplate() == true) {
return true;
} else {
return false;
}
} else {
return true;
}
}).map(column -> {
return Arrays.asList(column.getLabel());
}).collect(Collectors.toList());
}
return heads;
}
@Override
public boolean filterData(List list, List errorRows,ExcelUploadResult excelUploadResult) {
return true;
}
@Override
public List convertData(List list) {
return list;
}
/**
* 获取需要导出的字段,生成导出的数据列表
*/
private List> getDataList(List elTableColumns, List rows) {
List> dataList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(elTableColumns) && CollectionUtils.isNotEmpty(rows)) {
rows.forEach(row -> {
List
ElTableColumnUtil
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Description: ElementUi Util
* Company: 顺丰科技有限公司国际业务科技部
*
* @Author: 80003512
* Date: 2019/4/12 11:34
*/
public class ElTableColumnUtil {
/**
* 获取所有加注解的字段
*
* @param clazz
* @return
*/
public static List getAllTableColumns(Class clazz) {
List list = new ArrayList<>();
try {
Field[] fields = clazz.getDeclaredFields();
int i = 0;
for (Field field : fields) {
if (field.isAnnotationPresent(TableColumn.class)) {
String fieldName = field.getName();
TableColumn[] columns = field.getAnnotationsByType(TableColumn.class);
for (TableColumn column : columns) {
ElTableColumn elTableColumn = new ElTableColumn();
// 国际化label
String label = column.label();
if (StringUtils.isBlank(label)) {
elTableColumn.setLabel(fieldName);
} else {
elTableColumn.setLabel(I18nUtil.getMessage(label));
}
elTableColumn.setField(fieldName);
elTableColumn.setFormatter(column.formatter());
elTableColumn.setAlign(column.align());
elTableColumn.setIndex(i);
elTableColumn.setWidth(column.width());
elTableColumn.setEmpty(column.empty());
elTableColumn.setLength(column.length());
elTableColumn.setReg(column.reg());
elTableColumn.setErrorMsg(column.errorMsg());
elTableColumn.setCacheKey(column.cacheKey());
boolean export = column.export();
boolean display = column.display();
boolean template = column.template();
elTableColumn.setDisplay(display);
elTableColumn.setExport(export);
elTableColumn.setTemplate(template);
list.add(elTableColumn);
i++;
}
}
}
Collections.sort(list);
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}