赞
踩
EasyExcel是阿里出的一款基于Java的、快速、简洁、解决大文件内存溢出的处理Excel的开源工具,本文主要是使用这个工具对Excel导入导出进行讲解。
创建工具类EasyExcelUtil,将导出和导入的相关方法写入该类中,以下详细讲述使用EasyExcel导出、导入数据。
/** * 导出excel表格 * * @param response * @param dataList 数据列表 * @param clazz 数据对象 * @param fileName 文件名称 * @param sheetName sheet名称 * @throws Exception */ public static <T> void exportExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz, String fileName, String sheetName) throws Exception { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc()); EasyExcelFactory.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(dataList); }
/** * 导出excel表格-支设置单元格样式 * * @param response * @param dataList 数据列表 * @param clazz 数据对象 * @param fileName 文件名称 * @param sheetName sheet名称 * @param writeHandler 处理器 * @throws Exception */ public static <T> void exportWriteHandlerExcel(HttpServletResponse response, List<T> dataList, Class<T> clazz, String fileName, String sheetName, WriteHandler writeHandler) throws Exception { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc()); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), clazz).build(); // 这里注意如果同一个sheet只要创建一次 WriteSheet writeSheet = EasyExcel.writerSheet(sheetName) .registerWriteHandler(writeHandler).build(); excelWriter.write(dataList, writeSheet); excelWriter.finish(); }
/** * 导出多个sheet * * @param response * @param dataList 多个sheet数据列表 * @param clazzMap 对应每个sheet列表里面的数据对应的sheet名称 * @param fileName 文件名 * @param <T> * @throws Exception */ public static <T> void exportExcels(HttpServletResponse response, List<List<?>> dataList, Map<Integer, String> clazzMap, String fileName) throws Exception { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc()); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); int sheetNum = dataList.size(); for (int i = 0; i < sheetNum; i++) { List<?> objects = dataList.get(i); Class<?> aClass = objects.get(0).getClass(); WriteSheet writeSheet0 = EasyExcel.writerSheet(i, clazzMap.get(i)).head(aClass).build(); excelWriter.write(objects, writeSheet0); } excelWriter.finish(); }
此功能应用于导出同一个业务功能数据,但是每次导出的数据的表头是动态变化的,比如不同租户查看的同一个页面上展示的字段不一样,举个例子,租户A看到展示的字段是a、b、c;租户B看到展示字段是a、b。那么此时导出模板的表头字段就不一样。
<1>导出模板,传入一个处理器
/** * 导出excel表格-列不确定情况;只导出模板【一个处理器】 * * @param response * @param headList 表头列表 * @param dataList 数据列表 * @param fileName 文件名称 * @param sheetName sheet名称 * @param writeHandler 处理器 * @throws Exception */ public static <T> void exportExcel(HttpServletResponse response, List<List<String>> headList, List<List<T>> dataList, String fileName, String sheetName, WriteHandler writeHandler) throws Exception { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc()); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); // 这里注意如果同一个sheet只要创建一次 ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet(sheetName).head(headList); if (null != writeHandler) { sheetBuilder.registerWriteHandler(writeHandler); } WriteSheet writeSheet = sheetBuilder.build(); excelWriter.write(dataList, writeSheet); excelWriter.finish(); }
<2>导出模板,传入多个处理器
/** * 导出excel表格-列不确定情况;只导出模板【多个处理器】 * * @param response * @param headList 表头列表 * @param dataList 数据列表 * @param fileName 文件名称 * @param sheetName sheet名称 * @param writeHandlers 处理器集合 * @throws Exception */ public static <T> void exportExcel(HttpServletResponse response, List<List<String>> headList, List<List<T>> dataList, String fileName, String sheetName, List<WriteHandler> writeHandlers) throws Exception { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setHeader("Content-disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc()); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); // 这里注意如果同一个sheet只要创建一次 ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet(sheetName).head(headList); if (CollUtil.isNotEmpty(writeHandlers)) { for (WriteHandler writeHandler : writeHandlers) { sheetBuilder.registerWriteHandler(writeHandler); } } WriteSheet writeSheet = sheetBuilder.build(); excelWriter.write(dataList, writeSheet); excelWriter.finish(); }
填充数据的模板示例如下,注意:模板的格式需要加上字段对应的字段属性名称。样式举例:
说明:. 的{}就是根据自定义实体存值,{}里面是你自己写的字段
<1>填充列表数据
/** * 根据模板将集合对象填充表格 * * @param inputStream 模板文件输入流 * @param response 模板文件输出流 * @param list 填充对象集合 * @param fileName 文件名称 * @param sheetName 需要写入的sheet(不传:填充到第一个sheet) * @throws Exception */ public static <T> void exportTemplateExcelList(InputStream inputStream, HttpServletResponse response, List<T> list, String fileName, String sheetName) throws Exception { // 全部填充:全部加载到内存中一次填充 if (StringUtils.isBlank(sheetName)) { EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).sheet().doFill(list); } else { EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).sheet(sheetName).doFill(list); } }
<2>填充列表+自定义字段属性值数据
此场景适用于模板中的一些字段不是实体中字段,而是一些特殊处理的字段,比如说展示当前文档的一个指示说明,只是特别标注在文档中某一个位置就好了,这个不是集合中的数据,所以自定义字段需要进行特殊处理,他的处理是通过下面方法的入参object。
一个sheet
/** * 根据模板将集合对象填充表格-单个sheet * * @param inputStream 模板文件输入流 * @param list 填充对象集合-元素对应模板中的.{} * @param object 填充对象-元素对应模板中的{} * @param fileName 文件名称 * @throws Exception */ public static <T> void exportTemplateSheet(InputStream inputStream, HttpServletResponse response, List<T> list, Object object, String fileName) throws Exception { FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); ExcelWriter excelWriter = EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).build(); WriteSheet writeSheet0 = EasyExcelFactory.writerSheet(0).build(); // 全部填充:全部加载到内存中一次填充 excelWriter.fill(object, fillConfig, writeSheet0); excelWriter.fill(list, fillConfig, writeSheet0); // 可分多次填充,使用文件缓存(省内存) // excelWriter.fill(list1, writeSheet); // excelWriter.fill(list2, writeSheet); excelWriter.finish(); }
两个sheet
/** * 根据模板将集合对象填充表格-多个sheet * * @param inputStream 模板文件输入 * @param response 模板文件输出流 * @param list1 填充对象集合-元素对应模板中的.{} * @param list2 * @param object1 填充对象-元素对应模板中的{} * @param object2 * @param fileName 文件名称 * @throws Exception */ public static <T> void exportTemplateSheets(InputStream inputStream, HttpServletResponse response, List<T> list1, List<T> list2, Object object1, Object object2, String fileName) throws Exception { FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); ExcelWriter excelWriter = EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(inputStream).build(); WriteSheet writeSheet0 = EasyExcelFactory.writerSheet(0).build(); WriteSheet writeSheet1 = EasyExcelFactory.writerSheet(1).build(); excelWriter.fill(object1, fillConfig, writeSheet0); excelWriter.fill(list1, fillConfig, writeSheet0); excelWriter.fill(object2, fillConfig, writeSheet1); excelWriter.fill(list2, fillConfig, writeSheet1); excelWriter.finish(); }
/** * 构建输出流 * * @param fileName 文件名称 * @param response 模板文件输出流 * @param response * @return * @throws Exception */ private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception { fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()); response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setHeader("Content-Disposition", "attachment;filename=" + fileName + FileTypeEnum.TEMPLATE_SUFFIX.getDesc()); return response.getOutputStream(); }
/** * 导入-读取一页sheet * * @param inputStream 文件流 * @param clazz 数据对象 * @param sheetName 要读取的sheet (不传,默认读取第一个sheet) * @throws Exception */ public static <T> List<T> importExcel(InputStream inputStream, Class<T> clazz, String sheetName, ReadExcelListener readExcelListener) throws Exception { ExcelReaderBuilder builder = EasyExcelFactory.read(inputStream, clazz, readExcelListener); if (StringUtils.isBlank(sheetName)) { builder.sheet().doRead(); } else { builder.sheet(sheetName).doRead(); } return readExcelListener.getList(); }
/** * 导入-读取多个sheet * * @param inputStream 文件流 * @param sheetNum 需要读取的sheet个数(默认0开始,如果传入2,则读取0、1) * @param sheetObjList 每个sheet里面需要封装的对象(如果index为2,则需要传入对应的2个对象) * @param <T> * @return */ public static <T> List<List<T>> importExcels(InputStream inputStream, int sheetNum, List<T> sheetObjList, ReadExcelListener<T> readExcelListener) throws Exception { List<List<T>> resultList = new LinkedList<>(); for (int index = 0; index < sheetNum; index++) { Class<T> tClass = (Class<T>) sheetObjList.get(index).getClass(); EasyExcelFactory.read(inputStream, tClass, readExcelListener).sheet(index).doRead(); List<T> list = readExcelListener.getList(); resultList.add(list); } return resultList; }
CellWidthStyleHandler
package com.wm.easyexcel.util; import cn.hutool.core.collection.CollUtil; import com.alibaba.excel.enums.CellDataTypeEnum; import com.alibaba.excel.metadata.data.CellData; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.handler.context.CellWriteHandlerContext; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy; import org.apache.poi.ss.usermodel.Cell; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @ClassName: CellWidthStyleHandler * @Description: 设置表头的调整宽策略 * @Author: WM * @Date: 2022/12/23 14:37 */ public class CellWidthStyleHandler extends AbstractColumnWidthStyleStrategy { // 可以根据这里的最大宽度,按自己需要进行调整,搭配单元格样式实现类中的,自动换行,效果更好 private static final int MAX_COLUMN_WIDTH = 50; private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8); /** * 是否设置固定宽度 */ private boolean fixed; /** * 固定宽度 */ private int fixedWidth; public CellWidthStyleHandler() { } public CellWidthStyleHandler(boolean fixed, int fixedWidth) { this.fixed = fixed; this.fixedWidth = (fixedWidth == 0 ? 15 : fixedWidth); } @Override protected void setColumnWidth(CellWriteHandlerContext context) { boolean needSetWidth = context.getHead() || !CollUtil.isEmpty(context.getCellDataList()); if (!needSetWidth) { return; } WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder(); // 设置固定宽度 if (fixed) { Cell cell = context.getCell(); writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), fixedWidth * 256); return; } // 设置自动调整宽度 Map<Integer, Integer> maxColumnWidthMap = CACHE.get(writeSheetHolder.getSheetNo()); if (maxColumnWidthMap == null) { maxColumnWidthMap = new HashMap(16); CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap); } Cell cell = context.getCell(); int columnWidth = this.dataLength(context.getCellDataList(), cell, context.getHead()); if (columnWidth >= 0) { if (columnWidth > MAX_COLUMN_WIDTH) { columnWidth = MAX_COLUMN_WIDTH; } Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex()); if (maxColumnWidth == null || columnWidth > maxColumnWidth) { maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth); writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256); } } } private int dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) { if (isHead) { return cell.getStringCellValue().getBytes().length; } else { CellData cellData = cellDataList.get(0); CellDataTypeEnum type = cellData.getType(); if (type == null) { return -1; } else { switch (type) { case STRING: return cellData.getStringValue().getBytes().length; case BOOLEAN: return cellData.getBooleanValue().toString().getBytes().length; case NUMBER: return cellData.getNumberValue().toString().getBytes().length; default: return -1; } } } } }
DefaultExcelHeadHandler
package com.wm.easyexcel.util; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.handler.context.CellWriteHandlerContext; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import org.apache.poi.ss.usermodel.IndexedColors; /** * @ClassName: DefaultExcelHeadHandler * @Description: 默认自定义表头样式拦截器 * @Author: WM * @Date: 2022/12/22 17:35 */ public class DefaultExcelHeadHandler extends HorizontalCellStyleStrategy { //设置头样式 @Override protected void setHeadCellStyle(CellWriteHandlerContext context) { WriteCellStyle headWriteCellStyle = new WriteCellStyle(); headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 11); headWriteFont.setColor(IndexedColors.BLACK.getIndex()); headWriteCellStyle.setWriteFont(headWriteFont); if (stopProcessing(context)) { return; } WriteCellData<?> cellData = context.getFirstCellData(); WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle()); } }
ExcelHeadStyles
package com.wm.easyexcel.util; import lombok.Data; /** * @ClassName: ExcelHeadStyles * @Description: excel表头颜色定义 * @Author: WM * @Date: 2022/12/22 17:24 */ @Data public class ExcelHeadStyles { /** * 表头横坐标 - 行 */ private Integer rowIndex; /** * 表头纵坐标 - 列 */ private Integer columnIndex; /** * 内置颜色 */ private Short indexColor; /** * 字体颜色 */ private Short fontColor; public ExcelHeadStyles(Integer columnIndex, Short fontColor) { this.columnIndex = columnIndex; this.fontColor = fontColor; } public ExcelHeadStyles(Integer rowIndex, Integer columnIndex, Short fontColor) { this.rowIndex = rowIndex; this.columnIndex = columnIndex; this.fontColor = fontColor; } }
ReadExcelListener
package com.wm.easyexcel.util; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.exception.ExcelDataConvertException; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @ClassName: ReadExcelListener * @Description: 读取excel文件数据监听器 * @Author: WM * @Date: 2022/12/21 17:00 */ @Slf4j public abstract class ReadExcelListener<T> extends AnalysisEventListener<T> { private static final int BATCH_COUNT = 10000; /** * 数据集 */ private final List<T> list = new ArrayList<>(); /** * 表头数据 */ private Map<Integer, String> headMap = null; private Map<String, Object> params; // 获取数据集 public List<T> getList() { return this.list; } // 获取表头数据 public Map<Integer, String> getHeadMap() { return this.headMap; } /** * 每条数据都会进入 * * @param object * @param analysisContext */ @Override public void invoke(T object, AnalysisContext analysisContext) { this.list.add(object); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list list.clear(); } } /** * 数据解析完调用 * * @param analysisContext */ @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); log.info("所有数据解析完成!"); } /** * 读取到的表头信息 * * @param headMap * @param context */ @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { this.headMap = headMap; } /** * 异常时调用 * * @param exception: * @param context: * @throws Exception */ @Override public void onException(Exception exception, AnalysisContext context) throws Exception { // 数据解析异常 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception; throw new RuntimeException("第" + excelDataConvertException.getRowIndex() + "行" + excelDataConvertException.getColumnIndex() + "列" + "数据解析异常"); } // 其他异常... } /** * 数据存储到数据库 */ public abstract boolean saveData(); }
这里作为公共导入监听器,保存数据的方法saveData(),下放到各层具体的业务去继承实现。
创建学生和老师两个实体类
Student
package com.wm.easyexcel.entity; import com.alibaba.excel.annotation.ExcelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @ClassName: Student * @Description: 学生 * @Author: WM * @Date: 2021-07-22 21:30 **/ @Data @NoArgsConstructor @AllArgsConstructor public class Student { @ExcelProperty("序号") private Integer num; @ExcelProperty("学号") private String sno; @ExcelProperty("学生名字") private String name; @ExcelProperty("性别") private String sex; }
Teacher
package com.wm.easyexcel.entity; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * @ClassName: Teacher * @Description: 老师 * @Author: WM * @Date: 2021-07-22 21:30 **/ @Data public class Teacher { @ExcelProperty("编号") private String sno; @ExcelProperty("老师名字") private String name; @ExcelProperty("性别") private String sex; }
IStudentService
package com.wm.easyexcel.service; import com.wm.easyexcel.entity.Student; import java.util.List; import java.util.Map; /** * @ClassName: IStudentService * @Description: 学生相关接口 * @Author: WM * @Date: 2023/1/9 16:52 */ public interface IStudentService { /** * 查询学生集合信息 * * @return */ List<Student> listStudent(Long limit); /** * 批量保存学生数据 * * @param students * @return */ boolean batchSaveStudent(List<Student> students); /** * 批量保存学生数据-自行处理行列数据,不使用实体 * * @param dataList * @param headMap * @return */ boolean batchSaveStudent(List<Map<Integer, String>> dataList, Map<Integer, String> headMap); }
对应的实现类StudentServiceImpl
package com.wm.easyexcel.service.impl; import cn.hutool.core.lang.UUID; import com.wm.easyexcel.entity.Student; import com.wm.easyexcel.service.IStudentService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @ClassName: StudentServiceImpl * @Description: 学生相关接口实现 * @Author: WM * @Date: 2023/1/9 16:53 */ @Slf4j @Service public class StudentServiceImpl implements IStudentService { @Override public List<Student> listStudent(Long limit) { List<Student> students = new ArrayList<>(); for (int i = 0; i < limit; i++) { Student student = new Student(); student.setSex("男"); student.setNum(i); student.setName("小A" + i); student.setSno(UUID.fastUUID().toString()); students.add(student); } return students; } @Override public boolean batchSaveStudent(List<Student> students) { // 调用dao层接口保存数据... log.info("批量保存学生数据,共:{}条数据", students.size()); return true; } @Override public boolean batchSaveStudent(List<Map<Integer, String>> dataList, Map<Integer, String> headMap) { // 这里可以针对读取到的表头数据和对应的行数据,这种应用场景适用于导入的excel中数据需要我们自己解析做其他业务处理,或者表头列是动态的,我们 // 没有办法使用一个固定的实体做映射 log.info("表头列数据:{}", headMap.values()); log.info("批量保存学生数据,共:{}条数据", dataList.size()); return true; } }
ITeacherService
package com.wm.easyexcel.service; import com.wm.easyexcel.entity.Teacher; import java.util.List; import java.util.Map; /** * @ClassName: ITeacherService * @Description: 老师相关接口 * @Author: WM * @Date: 2023/1/9 16:52 */ public interface ITeacherService { /** * 查询老师集合信息 * * @return */ List<Teacher> listTeacher(Long limit); /** * 批量保存老师数据 * * @param Teachers * @return */ boolean batchSaveTeacher(List<Teacher> Teachers); }
对应的实现类TeacherServiceImpl
package com.wm.easyexcel.service.impl; import cn.hutool.core.lang.UUID; import com.wm.easyexcel.entity.Teacher; import com.wm.easyexcel.service.ITeacherService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * @ClassName: TeacherServiceImpl * @Description: 老师相关接口实现 * @Author: WM * @Date: 2023/1/9 16:53 */ @Slf4j @Service public class TeacherServiceImpl implements ITeacherService { @Override public List<Teacher> listTeacher(Long limit) { List<Teacher> teachers = new ArrayList<>(); for (int i = 0; i < limit; i++) { Teacher teacher = new Teacher(); teacher.setSex("女"); teacher.setName("老师A" + i); teacher.setSno(UUID.fastUUID().toString()); teachers.add(teacher); } return teachers; } @Override public boolean batchSaveTeacher(List<Teacher> teachers) { // 调用dao层接口保存数据... log.info("批量保存老师数据,共:{}条数据", teachers.size()); return true; } }
创建导出的Controller为工具类中各个导出方法的测试做准备。
package com.wm.easyexcel.controller; import cn.hutool.core.collection.CollUtil; import com.wm.easyexcel.entity.Student; import com.wm.easyexcel.handler.UserExcelHeadHandler; import com.wm.easyexcel.service.IStudentService; import com.wm.easyexcel.service.ITeacherService; import com.wm.easyexcel.strategy.RowMergeStrategy; import com.wm.easyexcel.util.CellWidthStyleHandler; import com.wm.easyexcel.util.EasyExcelUtil; import com.wm.easyexcel.util.ExcelHeadStyles; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.IndexedColors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.*; import java.util.stream.Collectors; /** * @ClassName: ExportController * @Description: 导出前端控制器 * @Author: WM * @Date: 2023/1/9 16:44 */ @Slf4j @RestController public class ExportController { @Resource private IStudentService studentService; @Resource private ITeacherService teacherService; private static final String[] USER_HEAD_FIELDS = new String[]{"序号", "学号", "姓名", "性别"}; /** * 导出学生数据-一个sheet * * @param response */ @GetMapping("/student/export/sheet") public void exportSheetStudent(HttpServletResponse response) { List<Student> students = studentService.listStudent(10L); log.info("单sheet学生数据开始导出..."); long startTimeL = System.currentTimeMillis(); try { EasyExcelUtil.exportExcel(response, students, Student.class, "导出学生数据-一个sheet", "第一页"); } catch (Exception e) { log.error("单sheet学生数据导出异常!", e); } log.info("单sheet学生数据导出完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /** * 导出学生数据-多个sheet * * @param response */ @GetMapping("/student/export/sheets") public void exportSheetsStudent(HttpServletResponse response) { log.info("多sheet学生数据开始导出..."); long startTimeL = System.currentTimeMillis(); try { List<List<?>> sheetList = new ArrayList<>(); List<Student> students1 = studentService.listStudent(10L); List<Student> students2 = studentService.listStudent(20L); sheetList.add(students1); sheetList.add(students2); Map<Integer, String> clazzMap = new HashMap<>(); clazzMap.put(0, "sheet1"); clazzMap.put(1, "sheet2"); EasyExcelUtil.exportExcels(response, sheetList, clazzMap, "导出学生数据-多个sheet"); } catch (Exception e) { log.error("多sheet学生数据导出失败!", e); } log.info("多sheet学生数据导出完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /** * 导出学生数据-重复行合并格式 * * @param response */ @GetMapping("/student/export/mergeRow") public void exportMergeRowStudent(HttpServletResponse response) { log.info("合并格式学生数据开始导出..."); long startTimeL = System.currentTimeMillis(); try { List<Student> students = studentService.listStudent(10L); List<String> mergeDataList = students.stream().map(Student::getSex).collect(Collectors.toList()); EasyExcelUtil.exportWriteHandlerExcel(response, students, Student.class, "学生重复数据表格合并", "第一页", new RowMergeStrategy(mergeDataList, 3)); } catch (Exception e) { log.error("合并格式学生数据导出失败!", e); } log.info("合并格式学生数据导出完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /** * 下载导入学生数据的模板【一个自定义Handler】 * * @param response */ @GetMapping("/student/downTemplate001") public void downTemplateStudent001(HttpServletResponse response) { log.info("设置单元格宽度-学生数据模板开始下载..."); long startTimeL = System.currentTimeMillis(); try { List<List<String>> headList = Arrays.stream(USER_HEAD_FIELDS).map(field -> CollUtil.newArrayList(field)). collect(Collectors.toList()); CellWidthStyleHandler columnWidthHandler = new CellWidthStyleHandler(true, 15); EasyExcelUtil.exportExcel(response, headList, Collections.EMPTY_LIST, "学生数据导入模板-设置单元格宽度", "student", columnWidthHandler); } catch (Exception e) { log.error("设置单元格宽度-学生数据模板下载失败!", e); } log.info("设置单元格宽度-学生数据模板下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /** * 下载导入学生数据的模板【多个自定义Handler】 * * @param response */ @GetMapping("/student/downTemplate002") public void downTemplateStudent002(HttpServletResponse response) { log.info("设置单元格宽度+字体-学生数据模板开始下载..."); long startTimeL = System.currentTimeMillis(); try { List<List<String>> headList = Arrays.stream(USER_HEAD_FIELDS).map(field -> CollUtil.newArrayList(field)). collect(Collectors.toList()); // 表头第一行的指定前3列字标红 ExcelHeadStyles excelHeadStyle = new ExcelHeadStyles(0, 3, IndexedColors.RED1.getIndex()); UserExcelHeadHandler headHandler = new UserExcelHeadHandler(excelHeadStyle); CellWidthStyleHandler columnWidthHandler = new CellWidthStyleHandler(true, 15); EasyExcelUtil.exportExcel(response, headList, Collections.EMPTY_LIST, "学生数据导入模板-设置单元格宽度+字体", "student", CollUtil.newArrayList(headHandler, columnWidthHandler)); } catch (Exception e) { log.error("设置单元格宽度+字体-学生数据模板下载失败!", e); } log.info("设置单元格宽度+字体-学生数据模板下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /**************以下填充模板数据的接口测试说明:可改为get请求,InputStream可使用本地文件输入流,然后变可通过浏览器下载。******************************/ /** * 根据模板将集合对象填充表格-list * * @param template * @param response */ @PostMapping("/student/template/fill-list") public void exportFillTemplateList(@RequestParam("template") MultipartFile template, HttpServletResponse response) { log.info("根据模板填充学生数据,list,文件开始下载..."); long startTimeL = System.currentTimeMillis(); try { List students = studentService.listStudent(10L); EasyExcelUtil.exportTemplateExcelList(template.getInputStream(), response, students, "填充学生模板数据-list", "students"); } catch (Exception e) { log.error("根据模板填充学生数据,list,文件下载失败!", e); } log.info("根据模板填充学生数据,list,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /** * 根据模板将集合对象填充表格-单个sheet * * @param template * @param response */ @PostMapping("/student/template/fill-sheet") public void exportFillTemplateSheet(@RequestParam("template") MultipartFile template, HttpServletResponse response) { log.info("根据模板填充学生数据,单个sheet,文件开始下载..."); long startTimeL = System.currentTimeMillis(); try { List<Student> students = studentService.listStudent(10L); Map<String, Object> map = new HashMap<>(3); map.put("startMonth", "2022-8-10"); map.put("endMonth", "2022-8-12"); EasyExcelUtil.exportTemplateSheet(template.getInputStream(), response, students, map, "填充学生模板数据-sheet"); } catch (Exception e) { log.error("根据模板填充学生数据,单个sheet,文件下载失败!", e); } log.info("根据模板填充学生数据,单个sheet,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /** * 根据模板将集合对象填充表格-多个sheet * * @param template * @param response */ @PostMapping("/student/template/fill-sheets") public void exportFillTemplateSheets(@RequestParam("template") MultipartFile template, HttpServletResponse response) { log.info("根据模板填充数据,多个sheet,文件开始下载..."); long startTimeL = System.currentTimeMillis(); try { List students = studentService.listStudent(10L); Map<String, Object> map = new HashMap<>(2); map.put("startMonth", "2022-8-10"); map.put("endMonth", "2022-8-12"); List teachers = teacherService.listTeacher(10L); Map<String, Object> map2 = new HashMap<>(1); map2.put("illustrate", "老师人员列表"); EasyExcelUtil.exportTemplateSheets(template.getInputStream(), response, students, teachers, map, map2, "填充学生模板数据-sheets"); } catch (Exception e) { log.error("根据模板填充数据,多个sheet,文件下载失败!", e); } log.info("根据模板填充数据,多个sheet,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } /********转为get请求测试示例********/ /** * get请求测试 * * @param response */ @GetMapping("/student/template/test") public void test(HttpServletResponse response) throws FileNotFoundException { File file = new File("C:\\Users\\Administrator\\Desktop\\test.xlsx"); FileInputStream inputStream = new FileInputStream(file); log.info("根据模板填充学生数据,单个sheet,文件开始下载..."); long startTimeL = System.currentTimeMillis(); try { List<Student> students = studentService.listStudent(10L); Map<String, Object> map = new HashMap<>(3); map.put("startMonth", "2022-8-10"); map.put("endMonth", "2022-8-12"); EasyExcelUtil.exportTemplateSheet(inputStream, response, students, map, "填充学生模板数据-sheet"); } catch (Exception e) { log.error("根据模板填充学生数据,单个sheet,文件下载失败!", e); } log.info("根据模板填充学生数据,单个sheet,文件下载完成:{}秒", (System.currentTimeMillis() - startTimeL) / 1000); } }
此方法是简单的做导出数据,导出的excel不做任何格式的修改。
导出结果如下:
此方法导出两个sheet的数据,导出的excel不做任何格式的修改。
导出结果如下:
此方法导出数据中,指定列的相邻行单元格有重复的内容会被合并成一个单元格。这里需要创建一个设定单元格合并策略类RowMergeStrategy:
package com.wm.easyexcel.strategy; import cn.hutool.core.collection.CollUtil; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.merge.AbstractMergeStrategy; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; import java.util.ArrayList; import java.util.List; /** * @ClassName: RowMergeStrategy * @Description: 自定义行合并策略 * @Author: WM * @Date: 2023/1/9 20:01 */ public class RowMergeStrategy extends AbstractMergeStrategy { /** * 分组,每几行合并一次 */ private List<Integer> exportFieldGroupCountList; /** * 目标合并列index */ private Integer targetColumnIndex; // 需要开始合并单元格的首行index private Integer rowIndex; // exportDataList为待合并目标列的值 public RowMergeStrategy(List<String> exportDataList, Integer targetColumnIndex) { this.exportFieldGroupCountList = getGroupCountList(exportDataList); this.targetColumnIndex = targetColumnIndex; } @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer integer) { if (null == rowIndex) { rowIndex = cell.getRowIndex(); } // 仅从首行以及目标列的单元格开始合并,忽略其他 if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == targetColumnIndex) { mergeGroupColumn(sheet); } } private void mergeGroupColumn(Sheet sheet) { int rowCount = rowIndex; for (Integer count : exportFieldGroupCountList) { if (count == 1) { rowCount += count; continue; } // 合并单元格 CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1, targetColumnIndex, targetColumnIndex); sheet.addMergedRegionUnsafe(cellRangeAddress); rowCount += count; } } // 该方法将目标列根据值是否相同连续可合并,存储可合并的行数 private List<Integer> getGroupCountList(List<String> exportDataList) { if (CollUtil.isEmpty(exportDataList)) { return new ArrayList<>(); } List<Integer> groupCountList = new ArrayList<>(); int count = 1; for (int i = 1; i < exportDataList.size(); i++) { if (exportDataList.get(i).equals(exportDataList.get(i - 1))) { count++; } else { groupCountList.add(count); count = 1; } } groupCountList.add(count); return groupCountList; } }
导出结果如下:
此方法是在(4)方法的基础上,通过ExcelHeadStyles、UserExcelHeadHandler增加了对表头字体的设置,这里处理的是表头第一行的指定前3列字标红。因为是这是指定业务个人化的一种样式,所以不放入公共样式包里。
UserExcelHeadHandler代码如下:
package com.wm.easyexcel.handler; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.handler.context.CellWriteHandlerContext; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import com.wm.easyexcel.util.ExcelHeadStyles; import org.apache.poi.ss.usermodel.IndexedColors; /** * @ClassName: UserExcelHeadHandler * @Description: 用户相关excel处理器 * @Author: WM * @Date: 2023/1/9 18:24 */ public class UserExcelHeadHandler extends HorizontalCellStyleStrategy { private ExcelHeadStyles excelHeadStyle; public UserExcelHeadHandler(ExcelHeadStyles excelHeadStyle) { this.excelHeadStyle = excelHeadStyle; } //设置头样式 @Override protected void setHeadCellStyle(CellWriteHandlerContext context) { int columnNo = excelHeadStyle.getColumnIndex(); WriteCellStyle headWriteCellStyle = new WriteCellStyle(); headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short) 11); if (context.getColumnIndex() <= columnNo) { headWriteFont.setColor(excelHeadStyle.getFontColor()); } else { headWriteFont.setColor(IndexedColors.BLACK.getIndex()); } headWriteCellStyle.setWriteFont(headWriteFont); if (stopProcessing(context)) { return; } WriteCellData<?> cellData = context.getFirstCellData(); WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle()); } }
导出结果如下:
此方法是根据上传的模板文件,将查询的数据填充到excel指定的名称的sheet,然后导出。
上传文件的模板(后面根据上传模板导出填充数据都是用的这个模板):
模板影样式如下:
导出结果如下:
此方法涉及到自定义字段数据的导出,这里是“开始日期”、“结束日期”。
模板样式如下:
导出结果如下:
此方法导出两个不同集合的数据,指定到对应的sheet。
学生的样式未变,老师模板格式如下:
导出结果如下:
创建导入的Controller为工具类中各个导入方法的测试做准备。
package com.wm.easyexcel.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import com.wm.easyexcel.entity.Student; import com.wm.easyexcel.entity.Teacher; import com.wm.easyexcel.listern.ImportUserListener; import com.wm.easyexcel.util.EasyExcelUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; /** * @ClassName: ImportController * @Description: 导入前端控制器 * @Author: WM * @Date: 2023/1/9 16:44 */ @Slf4j @RestController public class ImportController { @Resource private ImportUserListener importUserListener; /** * 导入一个sheet学生数据 * * @param file */ @PostMapping("/student/import/sheet") public boolean importSheetStudent(@RequestParam("file") MultipartFile file) { EasyExcelUtil.checkFile(file); log.info("一个sheet学生数据开始导入..."); long startTime = System.currentTimeMillis(); List<Student> dataList = null; try { dataList = EasyExcelUtil.importExcel(file.getInputStream(), null, null, importUserListener); // 数据录入 Assert.isFalse(CollUtil.isEmpty(dataList), "未解析到导入的数据!"); } catch (Exception e) { log.error("一个sheet学生数据导入失败!", e); } log.info("一个sheet学生数据导入成功,共:{}条,消耗总时间:{}秒", dataList.size(), ((System.currentTimeMillis() - startTime) / 1000)); return true; } /** * 导入多个sheet学生数据 * * @param file */ @PostMapping("/student/import/sheets") public boolean importSheets(@RequestParam("file") MultipartFile file) { EasyExcelUtil.checkFile(file); log.info("多个sheet数据开始导入..."); long startTime = System.currentTimeMillis(); List<Student> dataList = null; try { List sheetObjList = new ArrayList(2); sheetObjList.add(Student.class); sheetObjList.add(Teacher.class); dataList = EasyExcelUtil.importExcels(file.getInputStream(), 2, sheetObjList, importUserListener); // 数据录入 Assert.isFalse(CollUtil.isEmpty(dataList), "未解析到导入的数据!"); } catch (Exception e) { log.error("多个sheet数据导入失败!", e); } log.info("多个sheet数据导入成功,共:{}条,消耗总时间:{}秒", dataList.size(), ((System.currentTimeMillis() - startTime) / 1000)); return true; } }
此方法是将上传的学生数据excel文件解析,然后解析后的数据保存到数据库中。此处需要使用到的ImportUserListener继承了ReadExcelListener,其主要实现逻辑是将学生或老师的数据进行保存处理。
ImportUserListener代码如下:
package com.wm.easyexcel.listern; import cn.hutool.core.collection.CollUtil; import com.wm.easyexcel.entity.Student; import com.wm.easyexcel.entity.Teacher; import com.wm.easyexcel.service.IStudentService; import com.wm.easyexcel.service.ITeacherService; import com.wm.easyexcel.util.ReadExcelListener; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; /** * @ClassName: ImportStudentListener * @Description: 导入学生excel数据监听器 * @Author: WM * @Date: 2022/12/22 10:44 */ @Component public class ImportUserListener extends ReadExcelListener { @Resource private IStudentService studentService; @Resource private ITeacherService teacherService; /** * 实现数据保存逻辑 * * @return */ @Override public boolean saveData() { List dataList = getList(); if (CollUtil.isEmpty(dataList)) { return false; } Object obj = dataList.get(0); if (obj instanceof Student) { return studentService.batchSaveStudent(dataList); } else if (obj instanceof Teacher) { return teacherService.batchSaveTeacher(dataList); } return true; } }
此方法是将excel解析出来的学生和老师数据集合保存于数据库中,这里其实就是在(1)方法的基础上增加了循环处理的操作。
有些场景每次导入数据的表头字段是动态变化的,所以我们不能使用一个固定的实体去和excel表头字段做映射,那么这时候就需要我们自己去处理表头和表数据。这里使用到了ImportDataListener监听器,代码如下:
package com.wm.easyexcel.listern; import cn.hutool.core.collection.CollUtil; import com.wm.easyexcel.service.IStudentService; import com.wm.easyexcel.util.ReadExcelListener; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.List; import java.util.Map; /** * @ClassName: ImportDataListener * @Description: 导入数据监听器 * @Author: WM * @Date: 2023/1/9 19:27 */ @Component public class ImportDataListener extends ReadExcelListener { @Resource private IStudentService studentService; /** * 实现数据保存逻辑-直接操作表头和行数据 * * @return */ @Override public boolean saveData() { Map<Integer, String> headMap = getHeadMap(); List dataList = getList(); if (CollUtil.isEmpty(headMap) || CollUtil.isEmpty(dataList)) { return false; } return studentService.batchSaveStudent(dataList, headMap); } }
最终导出的文件如下所示:
本文主要是对EasyExcel导入导出使用的讲解,包括一些导出常用的单元格宽度以及内部字体样式设置,常规的导出以及不固定表头数据的导出,根据上传模板导出数据;常规的根据上传excel文件导入数据,以及根据非固定字段的excel文件上传导入数据。
本文章的demo地址:https://github.com/Deamer1102/excel-demo/tree/master/easyexcel
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。