Java操作excel表格,除了运用POI技术,基于EasyExcel,接下来我们来实战操作下自定义动态化导出excel,自定义动态化为自定义标题,合并单元格,废话不多说,直接上干货。
引入依赖:
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
普通导出(粘贴即用):
typescript
public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, List<?> list, Class<?> pojoClass) {
if (StringUtils.isBlank(fileName)) {
fileName = DateUtils.format(new Date());
}
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream(), pojoClass)
.registerConverter(new LongStringConverter())
.registerWriteHandler(new HorizontalCellStyleStrategy(null, getWriteCellStyle()))
.sheet(sheetName).doWrite(list);
} catch (Exception e) {
throw new ServiceException("导出文件失败:", e.getMessage());
}
}
单个Sheet页导出到本地(粘贴即用):
arduino
/**
* excel单个sheet导出
*/
public static <T> void export(String path, String fileName, Class<T> exportType, List<T> list) {
//创建文件
String filePath = getFilePath(path, fileName);
String sheetName = fileName.replace(".xlsx", "").replace(".xls", "");
EasyExcel.write(filePath, exportType).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(list);
}
多Sheet页导出到本地(粘贴即用):
scss
public static void exportMultipleSheet(String path, String fileName, List<Class<?>> tClassList, List<String> sheetNameList, List<List<Object>> resultList) {
//创建文件
String filePath = getFilePath(path, fileName);
File file = new File(filePath);
try {
FileOutputStream output = new FileOutputStream(file);
ExcelWriter excelWriter = EasyExcel.write(output).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
for (int i = 0; i < sheetNameList.size(); i++) {
WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetNameList.get(i)).head(tClassList.get(i)).build();
excelWriter.write(resultList.get(i), writeSheet);
}
//关闭流
excelWriter.finish();
output.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
typescript
public static String getFilePath(String path, String fileName) {
//创建文件夹
File fileOld = new File(path);
if (!fileOld.exists()) {
try {
FileUtil.mkdir(fileOld);
} catch (Exception e) {
throw new ServiceException("创建文件夹失败");
}
}
//写入文件绝对地址
return String.format("%s/%s", path, fileName);
}
自定义表头
第一步:构建表头
typescript
@Data
public class EasyExcelTitleInfo {
// 索引 即列顺序
private Integer index;
// 标题 支持多级
private List<String> title;
// 对应返回数据的字段名称
private String columnName;
}
第二步:引入工具类方法
typescript
public static void exportExcelCustomizationHead(HttpServletResponse response, String fileName, String sheetName, List<?> list, List<EasyExcelTitleInfo> titleInfoList) {
if (CollectionUtils.isEmpty(titleInfoList)) {
throw new ServiceException("标题不能为空");
}
if (StringUtils.isBlank(fileName)) {
fileName = DateUtils.format(new Date());
}
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
EasyExcel.write(response.getOutputStream())
// excel表头处理
.head(getHead(titleInfoList))
.registerConverter(new LongStringConverter())
// 这里定义统一列宽
.registerWriteHandler(new AbstractColumnWidthStyleStrategy() {
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
Sheet sheet = writeSheetHolder.getSheet();
sheet.setColumnWidth(cell.getColumnIndex(), 5120);
}
})
// 样式定义
.registerWriteHandler(new HorizontalCellStyleStrategy(null, getWriteCellStyle()))
.sheet(sheetName)
// excel数据处理
.doWrite(getDataList(list, titleInfoList));
} catch (Exception e) {
throw new ServiceException("导出文件失败:", e.getMessage());
}
}
ini
// 获取表头
private static List<List<String>> getHead(List<EasyExcelTitleInfo> titleInfoList) {
List<List<String>> result = Lists.newArrayList();
titleInfoList.stream().sorted(Comparator.comparingInt(EasyExcelTitleInfo::getIndex))
.forEach(title -> {
List<String> currHead = Lists.newArrayList();
currHead.addAll(title.getTitle());
result.add(currHead);
});
return result;
}
ini
// 转换数据
private static List<Map<Integer, String>> getDataList(List<?> list, List<EasyExcelTitleInfo> titleInfoList) {
List<Map<Integer, String>> dataList = Lists.newArrayList();
if (CollectionUtils.isEmpty(list)) {
return dataList;
}
List<String> columnList = Lists.newArrayList();
titleInfoList.stream().sorted(Comparator.comparingInt(EasyExcelTitleInfo::getIndex))
.forEach(x -> columnList.add(x.getColumnName()));
list.forEach(x -> {
Map<String, Object> map = BeanUtil.beanToMap(x);
Map<Integer, String> data = Maps.newLinkedHashMap();
for (int i = 0; i < columnList.size(); i++) {
String column = columnList.get(i);
if (map.containsKey(column)) {
Object o = map.get(column);
if (null == o) {
data.put(i, "");
} else if (o instanceof String) {
data.put(i, o.toString());
} else if (o instanceof Date) {
data.put(i, DateUtil.formatDateTime((Date) o));
} else {
data.put(i, o.toString());
}
} else {
data.put(i, "");
}
}
dataList.add(data);
});
return dataList;
}
scss
// 定义导出数据样式
private static WriteCellStyle getWriteCellStyle() {
WriteCellStyle writeCellStyle = new WriteCellStyle();
writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
writeCellStyle.setBorderTop(BorderStyle.THIN);
writeCellStyle.setBorderLeft(BorderStyle.THIN);
writeCellStyle.setBorderRight(BorderStyle.THIN);
writeCellStyle.setBorderBottom(BorderStyle.THIN);
return writeCellStyle;
}
导入
引入监听类
(此方法是为了解决读取合并单元格时,取值不统一的问题,另外去除整行都为空的数据)
csharp
public class EasyExcelBaseListener<T> extends AnalysisEventListener<T> {
private final static Log log = LogFactory.getLog(EasyExcelBaseListener.class);
/**
* 表头
*/
private Map<Integer, String> headMap;
/**
* 批量操作的数据
*/
private List<T> dataList = new ArrayList<>();
/**
* 动态获取表行数
*/
private int headRowNum = 0;
/**
* 获取所有合并单元格信息
*/
private final List<CellExtra> cellExtraList = new ArrayList<>();
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
headRowNum++;
}
/**
* 每次读取完一条数据触发的方法
*/
@Override
public void invoke(T t, AnalysisContext context) {
dataList.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("读取excel完成");
//读取完成 填充合并过的单元格
if (CollectionUtils.isNotEmpty(cellExtraList)) {
mergeExcelData(dataList, cellExtraList, headRowNum);
}
}
public Map<Integer, String> getHeadMap() {
return headMap;
}
public void setHeadMap(Map<Integer, String> headMap) {
this.headMap = headMap;
}
public List<T> getDataList() {
// 移除全部为空的行
if (CollectionUtils.isNotEmpty(dataList)) {
try {
for (int i = dataList.size() - 1; i >= 0; i--) {
T t = dataList.get(i);
List<Field> fields = Arrays.stream(t.getClass().getDeclaredFields())
.filter(f -> f.isAnnotationPresent(ExcelProperty.class))
.collect(Collectors.toList());
boolean lineAllNull = Boolean.TRUE;
for (Field field : fields) {
field.setAccessible(true);
Object o = field.get(t);
if (!ObjectUtil.isNull(o)) {
lineAllNull = Boolean.FALSE;
break;
}
}
if (lineAllNull) {
dataList.remove(i);
} else {
break;
}
}
} catch (Exception e) {
throw new ServiceException("读取数据失败");
}
}
return dataList;
}
public void setDataList(List<T> dataList) {
this.dataList = dataList;
}
@Override
public void extra(CellExtra extra, AnalysisContext context) {
CellExtraTypeEnum type = extra.getType();
if (type == CellExtraTypeEnum.MERGE) {
if (extra.getRowIndex() >= headRowNum) {
cellExtraList.add(extra);
}
}
}
/**
* 处理合并单元格数据,所有行以第一个为准
*/
private void mergeExcelData(List<T> excelDataList, List<CellExtra> cellExtraList, int headRowNum) {
cellExtraList.forEach(cellExtra -> {
int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNum;
int lastRowIndex = cellExtra.getLastRowIndex() - headRowNum;
int firstColumnIndex = cellExtra.getFirstColumnIndex();
int lastColumnIndex = cellExtra.getLastColumnIndex();
//获取初始值 合并单元格左上角的值
Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, excelDataList);
//设置值 把合并单元格左上角的值 设置到合并区域的每一个单元格
for (int i = firstRowIndex; i <= lastRowIndex; i++) {
for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
setInitValueToList(initValue, i, j, excelDataList);
}
}
});
}
/**
* 设置值 把合并单元格左上角的值 设置到合并区域的每一个单元格
*/
private void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
T object = data.get(rowIndex);
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (annotation != null) {
if (annotation.value()[annotation.value().length - 1].equals(headMap.get(columnIndex))) {
try {
field.set(object, filedValue);
break;
} catch (IllegalAccessException e) {
log.error("设置合并单元格的值异常:{}", e);
}
}
}
}
}
/**
* 获取初始值 合并单元格左上角的值
*/
private Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
Object filedValue = null;
T object = data.get(firstRowIndex);
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (annotation != null) {
if (annotation.value()[annotation.value().length - 1].equals(headMap.get(firstColumnIndex))) {
try {
filedValue = field.get(object);
break;
} catch (IllegalAccessException e) {
log.error("设置合并单元格的值异常:{}", e);
}
}
}
}
return filedValue;
}
}
导入--读取数据
(此方法附带校验表头功能)
scss
public static <T> List<T> readExcel(MultipartFile multipartFile, Class<T> tClass) {
EasyExcelBaseListener<T> listener = new EasyExcelBaseListener<>();
try {
readExcel(multipartFile, tClass, listener);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
List<T> dataList = listener.getDataList();
Map<Integer, String> headMap = listener.getHeadMap();
List<String> modelHeadList = excelHeadData(tClass);
for (int i = 0; i < modelHeadList.size(); i++) {
if (!headMap.containsKey(i) || !StringUtils.equals(headMap.get(i), modelHeadList.get(i))) {
throw new ServiceException("表头校验:导入模版不正确");
}
}
if (CollectionUtils.isEmpty(dataList)) {
throw new ServiceException("导入数据为空,请检查导入文件");
}
return dataList;
}
scss
public static void readExcel(MultipartFile file, Class clazz, AnalysisEventListener listener) throws Exception {
checkFile(file);
InputStream inputStream = null;
try {
inputStream = new BufferedInputStream(file.getInputStream());
// 校验表头
checkTableTitle(file, clazz);
// 解析每行结果在listener中处理
EasyExcelFactory.read(file.getInputStream(), clazz, listener)
.extraRead(CellExtraTypeEnum.MERGE)
.sheet().doRead();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
scss
/**
* 校验文件
*/
private static void checkFile(MultipartFile file) {
//判断文件是否存在
if (null == file) {
throw new ServiceException("文件不存在!");
}
String fileName = file.getOriginalFilename();
//判断文件是否是excel文件
if (StringUtils.isNotBlank(fileName) && !fileName.endsWith(TYPE_OF_XLS) && !fileName.endsWith(TYPE_OF_XLSX)) {
throw new ServiceException(fileName + "不是excel文件");
}
}
ini
private static <T> List<String> excelHeadData(Class<T> clazz) {
// 获取所有的私有属性
Field[] fields = clazz.getDeclaredFields();
// 遍历所有属性
List<String> excelHeadData = new ArrayList<>();
for (Field field : fields) {
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (null != annotation) {
excelHeadData.add(annotation.value()[0]);
}
}
return excelHeadData;
}
ini
public static void checkTableTitle(MultipartFile file, Class clazz) throws Exception {
ByteArrayInputStream is = new ByteArrayInputStream(file.getBytes());
ExcelReader reader = ExcelUtil.getReader(is);
List<List<Object>> read = reader.read();
List<Object> importTitleList = read.get(0);
if (CollectionUtils.isEmpty(importTitleList)) {
throw new ServiceException("导入表头为空");
}
Field[] declaredFields = clazz.getDeclaredFields();
List<String> tableTitleList = Lists.newArrayList();
for (Field field : declaredFields) {
if (null != field.getAnnotation(ExcelProperty.class)) {
String[] value = field.getAnnotation(ExcelProperty.class).value();
if (value.length > 0) {
tableTitleList.add(value[0]);
}
}
}
if (importTitleList.size() != tableTitleList.size()) {
throw new ServiceException("导入模板错误");
} else {
for (int i = 0; i < importTitleList.size(); i++) {
String s = importTitleList.get(i).toString().trim();
if (!s.equals(tableTitleList.get(i).trim())) {
throw new ServiceException("导入模板错误");
}
}
}
}