• easyExcel合并单元格导出


    效果图

    一、导入maven依赖

    (很多旧项目自定义了一套Excel导出工具,poi版本可能不兼容,一般poi新旧版本不兼容分界线在3.17,选择3.17版本不会发生代码不兼容情况)

    1. <dependency>
    2. <groupId>com.alibabagroupId>
    3. <artifactId>easyexcelartifactId>
    4. <version>2.2.6version>
    5. <exclusions>
    6. <exclusion>
    7. <groupId>org.apache.poigroupId>
    8. <artifactId>poiartifactId>
    9. exclusion>
    10. <exclusion>
    11. <groupId>org.apache.poigroupId>
    12. <artifactId>poi-ooxmlartifactId>
    13. exclusion>
    14. <exclusion>
    15. <groupId>org.apache.poigroupId>
    16. <artifactId>poi-ooxml-schemasartifactId>
    17. exclusion>
    18. exclusions>
    19. dependency>
    20. <dependency>
    21. <groupId>org.apache.poigroupId>
    22. <artifactId>poiartifactId>
    23. <version>3.17version>
    24. dependency>
    25. <dependency>
    26. <groupId>org.apache.poigroupId>
    27. <artifactId>poi-ooxmlartifactId>
    28. <version>3.17version>
    29. dependency>
    30. <dependency>
    31. <groupId>org.apache.poigroupId>
    32. <artifactId>poi-ooxmlartifactId>
    33. <version>3.17version>
    34. <classifier>sourcesclassifier>
    35. dependency>
    36. <dependency>
    37. <groupId>org.apache.commonsgroupId>
    38. <artifactId>commons-collections4artifactId>
    39. <version>4.1version>
    40. dependency>

    二、重写easyExcel-自定义写入处理器

    1. import com.alibaba.excel.metadata.Head;
    2. import com.alibaba.excel.write.merge.AbstractMergeStrategy;
    3. import org.apache.commons.collections4.CollectionUtils;
    4. import org.apache.poi.ss.usermodel.Cell;
    5. import org.apache.poi.ss.usermodel.Sheet;
    6. import org.apache.poi.ss.util.CellRangeAddress;
    7. import java.util.ArrayList;
    8. import java.util.List;
    9. /**
    10. * 自定义合并策略 该类继承了AbstractMergeStrategy抽象合并策略,需要重写merge()方法
    11. * @author reshui
    12. * @date 2023/9/4
    13. **/
    14. public class CustomMergeStrategy extends AbstractMergeStrategy {
    15. /**
    16. * 分组,每几行合并一次
    17. */
    18. private List exportFieldGroupCountList;
    19. /**
    20. * 目标合并列index
    21. */
    22. private Integer targetColumnIndex;
    23. /**
    24. * 需要开始合并单元格的首行index
    25. */
    26. private Integer rowIndex;
    27. /**
    28. * @param exportDataList exportDataList为待合并目标列的值
    29. * @param targetColumnIndex 需要合并的列
    30. * @return {@code }
    31. * @author reshui
    32. * @date 2023/09/05
    33. */
    34. public CustomMergeStrategy(List exportDataList, Integer targetColumnIndex) {
    35. this.exportFieldGroupCountList = getGroupCountList(exportDataList);
    36. this.targetColumnIndex = targetColumnIndex;
    37. }
    38. @Override
    39. protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
    40. if (null == rowIndex) {
    41. rowIndex = cell.getRowIndex();
    42. }
    43. // 仅从首行以及目标列的单元格开始合并,忽略其他
    44. if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == targetColumnIndex) {
    45. mergeGroupColumn(sheet);
    46. }
    47. }
    48. private void mergeGroupColumn(Sheet sheet) {
    49. int rowCount = rowIndex;
    50. for (Integer count : exportFieldGroupCountList) {
    51. if (count == 1) {
    52. rowCount += count;
    53. continue;
    54. }
    55. // 合并单元格
    56. CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1, targetColumnIndex, targetColumnIndex);
    57. sheet.addMergedRegionUnsafe(cellRangeAddress);
    58. rowCount += count;
    59. }
    60. }
    61. /**
    62. * 该方法将目标列根据值是否相同连续可合并,存储可合并的行数
    63. * @param exportDataList
    64. * @return {@code List }
    65. * @author reshui
    66. * @date 2023/09/05
    67. */
    68. private List getGroupCountList(List exportDataList) {
    69. if (CollectionUtils.isEmpty(exportDataList)) {
    70. return new ArrayList<>();
    71. }
    72. List groupCountList = new ArrayList<>();
    73. int count = 1;
    74. for (int i = 1; i < exportDataList.size(); i++) {
    75. if (exportDataList.get(i).equals(exportDataList.get(i - 1))) {
    76. count++;
    77. } else {
    78. groupCountList.add(count);
    79. count = 1;
    80. }
    81. }
    82. // 处理完最后一条后
    83. groupCountList.add(count);
    84. return groupCountList;
    85. }
    86. }

    三、导出工具类封装

    1. import cn.hutool.core.collection.CollUtil;
    2. import cn.hutool.core.date.DateUtil;
    3. import cn.hutool.core.io.FileUtil;
    4. import cn.hutool.core.util.StrUtil;
    5. import com.alibaba.excel.EasyExcel;
    6. import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
    7. import com.alibaba.excel.write.handler.WriteHandler;
    8. import com.alibaba.fastjson.JSON;
    9. import com.alibaba.fastjson.JSONObject;
    10. import org.slf4j.Logger;
    11. import org.slf4j.LoggerFactory;
    12. import javax.servlet.http.HttpServletResponse;
    13. import java.io.File;
    14. import java.io.IOException;
    15. import java.net.URLEncoder;
    16. import java.util.*;
    17. /**
    18. * 下载excel文件工具
    19. *
    20. * @author reshui
    21. * @date 2023/09/05
    22. */
    23. public class DownloadExcelUtil {
    24. private static final Logger log = LoggerFactory.getLogger(DownloadExcelUtil.class);
    25. private final static String separatorChar = "-";
    26. public static void downloadFile(HttpServletResponse response, Class clazz, String data) throws IOException {
    27. String timeStamp = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss");
    28. String fileName = timeStamp + "-log";
    29. downloadFile(response, clazz, data, fileName, "数据", null);
    30. }
    31. /**
    32. * @param response 响应请求
    33. * @param clazz 导出数据类型
    34. * @param data 数据源
    35. * @param customFileName 文件名
    36. * @param sheetName 页名
    37. * @param writeHandlerList 自定义写入处理器
    38. * @return {@code T }
    39. * @author reshui
    40. * @date 2023/09/05
    41. */
    42. public static T downloadFile(HttpServletResponse response, Class clazz
    43. , String data
    44. , String customFileName
    45. , String sheetName
    46. , List writeHandlerList) throws IOException {
    47. // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
    48. try {
    49. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    50. response.setCharacterEncoding("utf-8");
    51. // 这里URLEncoder.encode可以防止中文乱码 当然和easy-excel没有关系
    52. String fileName = URLEncoder.encode(customFileName, "UTF-8").replaceAll("\\+", "%20");
    53. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    54. // 这里需要设置不关闭流
    55. ExcelWriterSheetBuilder writerSheetBuilder = EasyExcel.write(response.getOutputStream(), clazz).autoCloseStream(Boolean.FALSE).sheet(sheetName);
    56. if (CollUtil.isNotEmpty(writeHandlerList)) {
    57. for (WriteHandler writeHandler : writeHandlerList) {
    58. writerSheetBuilder.registerWriteHandler(writeHandler);
    59. }
    60. }
    61. writerSheetBuilder.doWrite(JSONObject.parseArray(data, clazz));
    62. } catch (Exception e) {
    63. // 重置response
    64. response.reset();
    65. response.setContentType("application/json");
    66. response.setCharacterEncoding("utf-8");
    67. Map map = new HashMap<>(2);
    68. map.put("status", "failure");
    69. map.put("message", "下载文件失败" + e.getMessage());
    70. response.getWriter().println(JSON.toJSONString(map));
    71. }
    72. return null;
    73. }
    74. /**
    75. * 出把excel
    76. *
    77. * @param timeStamp 时间戳
    78. * @param excelFileName excel文件名字
    79. * @param headClassType 头类类型
    80. * @param resultExcelList 结果excel表
    81. * @param filePath 文件路径
    82. * @author reshui
    83. * @date 2023/02/15
    84. */
    85. public static void outputExcelToLocal(String timeStamp, String excelFileName, Class headClassType, List resultExcelList, String filePath) {
    86. //文件时间戳
    87. timeStamp = Objects.nonNull(timeStamp) ? timeStamp : StrUtil.EMPTY;
    88. String partFileName = filePath + File.separator + excelFileName + separatorChar + timeStamp + "-log.xlsx";
    89. FileUtil.touch(partFileName);
    90. EasyExcel.write(partFileName, headClassType).sheet("源数据").doWrite(resultExcelList);
    91. }
    92. /**
    93. * 简单把excel
    94. *
    95. * @param excelFileName excel文件名字
    96. * @param headClassType 头类类型
    97. * @param resultExcelList 结果excel表
    98. * @param filePath 文件路径
    99. * @author reshui
    100. * @date 2023/02/15
    101. */
    102. public static void easyOutputExcelToLocal(String excelFileName, Class headClassType, List resultExcelList, String filePath) {
    103. String timeStamp = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss");
    104. outputExcelToLocal(timeStamp, excelFileName, headClassType, resultExcelList, filePath);
    105. }
    106. public static void main(String[] args) {
    107. String timeStamp = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss");
    108. String titleTmpDirPath = FileUtil.getTmpDirPath() + File.separator + "test" + File.separator + timeStamp + File.separator;
    109. try {
    110. System.out.println("日志存储地址-[" + titleTmpDirPath + "]");
    111. log.info("日志存储[" + titleTmpDirPath + "]");
    112. String partFileName = titleTmpDirPath + timeStamp + "-log.xlsx";
    113. FileUtil.touch(partFileName);
    114. EasyExcel.write(partFileName, String.class).sheet("源数据").doWrite(null);
    115. } catch (Exception e) {
    116. log.error("日志存储[" + titleTmpDirPath + "]" + "-error:", e);
    117. }
    118. }
    119. }

    四、运行

    1. @RestController
    2. @RequestMapping("/check")
    3. public class TestController {
    4. @RequestMapping(value = "/run1",method = {RequestMethod.GET})
    5. public void run1(HttpServletResponse response) throws IOException {
    6. List demoDataList = data1();
    7. List customMergeStrategies = Arrays.asList(
    8. new CustomMergeStrategy(demoDataList.stream().map(DemoData::getName).collect(Collectors.toList()), 1));
    9. DownloadExcelUtil.downloadFile(response,DemoData.class, JSONObject.toJSONString(demoDataList),"测试","页1"
    10. , customMergeStrategies);
    11. }
    12. public static List data1(){
    13. List demoDataList = new ArrayList<>();
    14. DemoData demoData0 = new DemoData();
    15. demoData0.setId("0");
    16. demoData0.setName("hello0");
    17. demoDataList.add(demoData0);
    18. DemoData demoData1 = new DemoData();
    19. demoData1.setId("1");
    20. demoData1.setName("hello1");
    21. demoDataList.add(demoData1);
    22. DemoData demoData11 = new DemoData();
    23. demoData11.setId("1");
    24. demoData11.setName("hello1");
    25. demoDataList.add(demoData11);
    26. DemoData demoData2 = new DemoData();
    27. demoData2.setId("2");
    28. demoData2.setName("hello2");
    29. demoDataList.add(demoData2);
    30. return demoDataList;
    31. }
    32. }

  • 相关阅读:
    新版本Spring Security 2.7 + 用法,直接旧正版粘贴
    【C++】60 alignas 和[nodiscard]
    sentinel 网关
    MySQL(12):MySQL数据类型
    不需要报班学课程,也能制作手办创业的新方法!
    MIT课程分布式系统学习07——Fault Tolerance raft2
    FTP(数据共享)
    java中fastJSON解析复合
    成都优优聚为什么值得信任?
    数据可视化基础与应用-01-课程目标与职位分析
  • 原文地址:https://blog.csdn.net/weixin_42477252/article/details/132695014