设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间20 现在时间20 缓存数据 和聪明人交流,和靠谱的人恋爱,和进取的人共事,和幽默的人随行。晚安!

和聪明人交流,和靠谱的人恋爱,和进取的人共事,和幽默的人随行。晚安!

查看: 905|回复: 2

Java导出Excel动态表头的示例详解

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:204
  • 打卡月天数:0
  • 打卡总奖励:3160
  • 最近打卡:2023-08-27 09:35:25
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
402
主题
369
精华
0
金钱
4342
积分
807
注册时间
2022-12-20
最后登录
2025-8-30

发表于 2025-2-8 23:57:26 来自手机 | 显示全部楼层 |阅读模式
目录
  • 前言
  • 一、效果展示
  • 二、代码实现
    • 1.固定头实体类
    • 2.动态头实现
    • 3.导出动态头

前言

本文只记录大致思路以及做法,代码不进行详细输出

场景:

模板导出

1.按照模板内容类型分组(分sheet):1.文本消息;2.文本卡片;3.富文本;4.图文

2.每个类型的动态参数不同,即为每个sheet的表头不同

一、效果展示

1.png

2.png

二、代码实现

1.固定头实体类

  1. @Data
  2. @ApiModel
  3. @ExcelIgnoreUnannotated
  4. // @HeadRowHeight(value = 50)
  5. // @ContentRowHeight(value = 50)
  6. @ColumnWidth(value = 20)
  7. public class MsgModuleInfoDTO {
  8. @ApiModelProperty(value = "模板id")
  9. private Long id;
  10. @ApiModelProperty(value = "模板ids")
  11. private List<Long> ids;
  12. @ApiModelProperty(value = "模板编码")
  13. @ExcelProperty(value = "模板编码")
  14. private String code;
  15. @ApiModelProperty(value = "模板名称")
  16. @ExcelProperty(value = "模板名称")
  17. private String name;
  18. @ApiModelProperty(value = "模板关联的渠道内容类型Code")
  19. private String contentTypeCode;
  20. @ApiModelProperty(value = "模板关联的渠道内容类型Value")
  21. @ExcelProperty(value = "内容类型")
  22. private String contentTypeValue;
  23. @ApiModelProperty(value = "业务场景")
  24. @ExcelProperty(value = "业务场景")
  25. private String condition;
  26. @ApiModelProperty(value = "所属应用id")
  27. private Integer appId;
  28. @ApiModelProperty(value = "所属应用名称")
  29. @ExcelProperty(value = "所属应用")
  30. private String appName;
  31. @ApiModelProperty(value = "是否启用(1:启用 ;0:不启用)")
  32. @ExcelProperty(value = "是否启用")
  33. private Integer isEnable;
  34. @ApiModelProperty(value = "app_消息跳转url")
  35. private String appUrl;
  36. @ApiModelProperty(value = "pc_消息跳转url")
  37. private String pcUrl;
  38. @ApiModelProperty(value = "模板标题")
  39. @ExcelProperty(value = "模板标题")
  40. private String title;
  41. @ApiModelProperty(value = "模板内容")
  42. @ExcelProperty(value = "模板内容")
  43. private String content;
  44. @ApiModelProperty(value = "富文本模板内容")
  45. @ExcelProperty(value = "富文本模板内容")
  46. private String richContent;
  47. private MessageTemplateDynamicProperties dynamicProperties;
  48. @ApiModelProperty(value = "修改时间")
  49. private LocalDateTime lastUpdateTime;
  50. @ApiModelProperty(value = "修改者用户ID")
  51. private Long lastUpdateUser;
  52. @ApiModelProperty(value = "修改者用户名称")
  53. private String lastUpdateUserName;
  54. @ApiModelProperty(value = "是否系统预设(1:是;0:不是)")
  55. @ExcelProperty(value = "是否系统预设", converter = MsgSystemConverter.class)
  56. private Integer isSystemType;
  57. @ApiModelProperty(value = "模板类型编码")
  58. private String msgFormCode;
  59. @ApiModelProperty(value = "模板类型名称")
  60. @ExcelProperty(value = "模板类型")
  61. private String msgFormName;
  62. }
复制代码

2.动态头实现

  1. @Getter
  2. @RequiredArgsConstructor(staticName = "of")
  3. public class CodeAndValue {
  4. private final String code;
  5. private final String name;
  6. private final Object value;
  7. }
  8. /**
  9. 渠道动态配置属性数据提供接口
  10. */
  11. public interface DynamicPropertiesGenerator {
  12. /**
  13. * 获取动态配置字段信息
  14. *
  15. * @return List<DynamicProperties>
  16. */
  17. @ApiModelProperty(hidden = true)
  18. @JsonIgnore
  19. List<CodeAndValue> getDynamicPropertiesList();
  20. }
  21. @ApiModel("模板动态字段配置数据")
  22. @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "contentType")
  23. @JsonSubTypes(value = {
  24. @JsonSubTypes.Type(value = MessageRichTextConfigurationDynamicProperties.class, name = "richTextMessage"),
  25. @JsonSubTypes.Type(value = MessageTextConfigurationDynamicProperties.class, name = "textMessage"),
  26. @JsonSubTypes.Type(value = MessageCardConfigurationDynamicProperties.class, name = "textCardMessage"),
  27. @JsonSubTypes.Type(value = MessagePictureConfigurationDynamicProperties.class, name = "pictureMessage")
  28. })
  29. public interface MessageTemplateDynamicProperties extends DynamicPropertiesGenerator {
  30. }
复制代码

MessageRichTextConfigurationDynamicProperties 富文本动态参数

  1. @Getter
  2. public class MessageRichTextConfigurationDynamicProperties implements MessageTemplateDynamicProperties {
  3. private final List<CodeAndValue> dynamicPropertiesList;
  4. @JsonIgnore
  5. private final MessageRichConfiguration messageCardConfiguration;
  6. @JsonCreator
  7. public MessageRichTextConfigurationDynamicProperties(@JsonProperty String messagePlatformRedirectUri,
  8. @JsonProperty Boolean messagePlatformRedirectWithAgileUserInfo,
  9. @JsonProperty String agileAppRedirectUri,
  10. @JsonProperty String agileMainRedirectUri) {
  11. this.messageCardConfiguration = new MessageRichConfiguration(messagePlatformRedirectUri, messagePlatformRedirectWithAgileUserInfo, agileAppRedirectUri, agileMainRedirectUri);
  12. this.dynamicPropertiesList = DynamicValueUtil.configurationToDynamicProperties(messageCardConfiguration, Mapper.values());
  13. }
  14. public String getMessagePlatformRedirectUri() {
  15. return messageCardConfiguration.getMessagePlatformRedirectUri();
  16. }
  17. public Boolean getMessagePlatformRedirectWithAgileUserInfo() {
  18. return messageCardConfiguration.getMessagePlatformRedirectWithAgileUserInfo();
  19. }
  20. public String getAgileAppRedirectUri() {
  21. return messageCardConfiguration.getAgileAppRedirectUri();
  22. }
  23. public String getAgileMainRedirectUri() {
  24. return messageCardConfiguration.getAgileMainRedirectUri();
  25. }
  26. @Data
  27. @AllArgsConstructor
  28. @NoArgsConstructor
  29. public static class MessageRichConfiguration {
  30. @ExcelProperty(value = "第三方平台富文本消息链接跳转地址")
  31. private String messagePlatformRedirectUri;
  32. @ExcelProperty(value = "第三方平台富文本消息链接跳转是否携带agile用户信息")
  33. private Boolean messagePlatformRedirectWithAgileUserInfo;
  34. @ExcelProperty(value = "Agile H5跳转地址")
  35. private String agileAppRedirectUri;
  36. @ExcelProperty(value = "Agile PC跳转地址")
  37. private String agileMainRedirectUri;
  38. }
  39. @Getter
  40. @RequiredArgsConstructor
  41. enum Mapper implements DynamicValueMapper {
  42. MESSAGE_PLATFORM_REDIRECT_URI(LambdaUtil.getFieldName(MessageRichConfiguration::getMessagePlatformRedirectUri), "第三方平台富文本消息链接跳转地址"),
  43. MESSAGE_PLATFORM_REDIRECT_WITH_AGILE_USERINFO(LambdaUtil.getFieldName(MessageRichConfiguration::getMessagePlatformRedirectWithAgileUserInfo), "第三方平台富文本消息链接跳转是否携带agile用户信息"),
  44. AGILE_APP_REDIRECT_URI(LambdaUtil.getFieldName(MessageRichConfiguration::getAgileAppRedirectUri), "Agile H5跳转地址"),
  45. AGILE_MAIN_REDIRECT_URI(LambdaUtil.getFieldName(MessageRichConfiguration::getAgileMainRedirectUri), "Agile PC跳转地址"),
  46. ;
  47. private final String code;
  48. private final String name;
  49. }
  50. }
复制代码

MessageCardConfigurationDynamicProperties 文本卡片动态参数

  1. @Getter
  2. @SuppressWarnings("unused")
  3. public class MessageCardConfigurationDynamicProperties implements MessageTemplateDynamicProperties {
  4. private final List<CodeAndValue> dynamicPropertiesList;
  5. @JsonIgnore
  6. private final MessageCardConfiguration messageCardConfiguration;
  7. @JsonCreator
  8. public MessageCardConfigurationDynamicProperties(@JsonProperty Boolean enableOauth2Link,
  9. @JsonProperty String btnTxt,
  10. @JsonProperty String messagePlatformRedirectUri,
  11. @JsonProperty Boolean messagePlatformRedirectWithAgileUserInfo,
  12. @JsonProperty String agileAppRedirectUri,
  13. @JsonProperty String agileMainRedirectUri) {
  14. this.messageCardConfiguration = new MessageCardConfiguration(enableOauth2Link, btnTxt, messagePlatformRedirectUri, messagePlatformRedirectWithAgileUserInfo,
  15. agileAppRedirectUri, agileMainRedirectUri);
  16. this.dynamicPropertiesList = DynamicValueUtil.configurationToDynamicProperties(messageCardConfiguration, Mapper.values());
  17. }
  18. public Boolean getEnableOauth2Link() {
  19. return messageCardConfiguration.getEnableOauth2Link();
  20. }
  21. public String getBtnTxt() {
  22. return messageCardConfiguration.getBtnTxt();
  23. }
  24. public String getMessagePlatformRedirectUri() {
  25. return messageCardConfiguration.getMessagePlatformRedirectUri();
  26. }
  27. public Boolean getMessagePlatformRedirectWithAgileUserInfo() {
  28. return messageCardConfiguration.getMessagePlatformRedirectWithAgileUserInfo();
  29. }
  30. public String getAgileAppRedirectUri() {
  31. return messageCardConfiguration.getAgileAppRedirectUri();
  32. }
  33. public String getAgileMainRedirectUri() {
  34. return messageCardConfiguration.getAgileMainRedirectUri();
  35. }
  36. @Data
  37. @AllArgsConstructor
  38. @NoArgsConstructor
  39. public static class MessageCardConfiguration {
  40. @ExcelProperty(value = "是否开启跳转链接")
  41. private Boolean enableOauth2Link;
  42. @ExcelProperty(value = "卡片消息跳转描述")
  43. private String btnTxt;
  44. @ExcelProperty(value = "平台跳转链接")
  45. private String messagePlatformRedirectUri;
  46. @ExcelProperty(value = "是否携带Agile用户信息")
  47. private Boolean messagePlatformRedirectWithAgileUserInfo;
  48. @ExcelProperty(value = "Agile H5跳转地址")
  49. private String agileAppRedirectUri;
  50. @ExcelProperty(value = "Agile PC跳转地址")
  51. private String agileMainRedirectUri;
  52. }
  53. @Getter
  54. @RequiredArgsConstructor
  55. enum Mapper implements DynamicValueMapper {
  56. ENABLE_OAUTH2_LINK(LambdaUtil.getFieldName(MessageCardConfiguration::getEnableOauth2Link), "跳转链接开启Oauth2授权"),
  57. BTN_TXT(LambdaUtil.getFieldName(MessageCardConfiguration::getBtnTxt), "卡片消息跳转描述"),
  58. MESSAGE_PLATFORM_REDIRECT_URI(LambdaUtil.getFieldName(MessageCardConfiguration::getMessagePlatformRedirectUri), "平台跳转链接"),
  59. MESSAGE_PLATFORM_REDIRECT_WITH_AGILE_USERINFO(LambdaUtil.getFieldName(MessageCardConfiguration::getMessagePlatformRedirectWithAgileUserInfo), "是否携带Agile用户信息"),
  60. AGILE_APP_REDIRECT_URI(LambdaUtil.getFieldName(MessageCardConfiguration::getAgileAppRedirectUri), "Agile H5 跳转路由"),
  61. AGILE_MAIN_REDIRECT_URI(LambdaUtil.getFieldName(MessageCardConfiguration::getAgileMainRedirectUri), "Agile PC 跳转路由"),
  62. ;
  63. private final String code;
  64. private final String name;
  65. }
  66. }
复制代码

3.导出动态头

  1. /**
  2. * 按照模板内容类型分组(分sheet):1.文本消息;2.文本卡片;3.富文本;4.图文
  3. *
  4. * @param queryDto 查询条件
  5. * @param response 响应
  6. * @author zhumq
  7. * @date 2025/01/22 14:30:17
  8. */
  9. @Override
  10. @SneakyThrows
  11. public void exportExcel(QueryMsgModuleInfoDTO queryDto, HttpServletResponse response) {
  12. log.info("【模板导出】导出模板数据,传入参数[queryDto = {}]", queryDto);
  13. // 获取模板数据
  14. Page<MsgModuleInfoEntity> page = convertPageBean(new Paging(1, -1));
  15. List<MsgModuleInfoDTO> moduleList = msgModuleInfoMapper.selectPageInfo(queryDto, page);
  16. if (CollUtil.isEmpty(moduleList)) {
  17. log.info("【模板导出】查询不到可导出的模板,传入参数[queryDto = {}]", JsonUtil.toJson(queryDto));
  18. response.setContentType("application/json");
  19. response.setCharacterEncoding("utf-8");
  20. response.getWriter().println(JSONUtil.toJsonStr(ApiResult.failMessage("没有数据可以导出")));
  21. return;
  22. }
  23. // 设置响应
  24. response.setCharacterEncoding("UTF-8");
  25. response.setContentType("application/vnd.ms-excel");
  26. String fileName = URLEncoder.encode("消息模板_" + System.currentTimeMillis(), "UTF-8");
  27. response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
  28. ServletOutputStream outputStream = null;
  29. ExcelWriter excelWriter = null;
  30. try {
  31. outputStream = response.getOutputStream();
  32. excelWriter = EasyExcel.write(outputStream).build();
  33. // 按内容类型分组
  34. Map<String, List<MsgModuleInfoDTO>> contentMap = moduleList.stream()
  35. .collect(Collectors.groupingBy(MsgModuleInfoDTO::getContentTypeCode));
  36. for (Map.Entry<String, List<MsgModuleInfoDTO>> entry : contentMap.entrySet()) {
  37. String contentType = entry.getKey();
  38. String sheetName = MsgEnum.ContentType.getLabelByKey(contentType);
  39. List<MsgModuleInfoDTO> items = entry.getValue();
  40. // 动态生成表头
  41. Map<String, Field> headMap = this.generateHeader(items);
  42. List<List<String>> head = headMap.keySet().stream()
  43. .map(Collections::singletonList)
  44. .collect(Collectors.toList());
  45. // 提取数据行
  46. List<List<Object>> dataList = this.obtainExportData(items, headMap);
  47. // 写入数据到不同的 sheet 使用动态生成的表头
  48. WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
  49. .head(head)
  50. .registerWriteHandler(new CustomColumnWidthStyleStrategy())
  51. .build();
  52. excelWriter.write(dataList, writeSheet);
  53. }
  54. // 刷新输出流
  55. outputStream.flush();
  56. } catch (IOException e) {
  57. log.error("导出模板数据失败", e);
  58. response.reset();
  59. response.setContentType("application/json");
  60. response.setCharacterEncoding("utf-8");
  61. response.getWriter().println(JSONUtil.toJsonStr(ApiResult.failMessage("下载文件失败")));
  62. } finally {
  63. // 关闭 ExcelWriter
  64. if (excelWriter != null) {
  65. excelWriter.finish();
  66. }
  67. // 关闭输出流
  68. if (outputStream != null) {
  69. try {
  70. outputStream.close();
  71. } catch (IOException e) {
  72. log.error("关闭输出流失败", e);
  73. }
  74. }
  75. }
  76. }
  77. /**
  78. * 提取数据行
  79. *
  80. * @param items
  81. * @param headMap
  82. * @return {@link List }<{@link List }<{@link Object }>>
  83. * @author zhumq
  84. * @date 2025/01/22 14:59:11
  85. */
  86. private List<List<Object>> obtainExportData(List<MsgModuleInfoDTO> items, Map<String, Field> headMap) {
  87. List<List<Object>> dataList = new ArrayList<>();
  88. for (MsgModuleInfoDTO item : items) {
  89. List<Object> dataListRow = new ArrayList<>();
  90. // 填充固定字段
  91. for (Map.Entry<String, Field> entryField : headMap.entrySet()) {
  92. String fieldName = entryField.getKey();
  93. Field field = entryField.getValue();
  94. if (field != null) {
  95. // 固定字段通过反射获取
  96. try {
  97. field.setAccessible(true);
  98. Object value = field.get(item);
  99. dataListRow.add(this.convertValue(value));
  100. } catch (Exception e) {
  101. log.error("反射获取字段值失败: {}", fieldName, e);
  102. dataListRow.add("");
  103. }
  104. } else {
  105. // 动态字段通过getDynamicProperties获取
  106. Object value = Optional.ofNullable(item.getDynamicProperties())
  107. .map(MessageTemplateDynamicProperties::getDynamicPropertiesList)
  108. .orElse(Collections.emptyList())
  109. .stream()
  110. .filter(cv -> cv.getName().equals(fieldName))
  111. .findFirst()
  112. .map(CodeAndValue::getValue)
  113. .orElse("");
  114. dataListRow.add(this.convertValue(value));
  115. }
  116. }
  117. dataList.add(dataListRow);
  118. }
  119. return dataList;
  120. }
  121. /**
  122. * 生成Excel表头结构
  123. *
  124. * @param items 模板数据
  125. * @return {@link Map }<{@link String }, {@link Field }>
  126. * @author zhumq
  127. * @date 2025/01/22 14:30:06
  128. */
  129. private Map<String, Field> generateHeader(List<MsgModuleInfoDTO> items) {
  130. // 1. 固定字段(通过反射获取DTO的@ExcelProperty)
  131. Map<String, Field> headerMap = new LinkedHashMap<>(this.getExcelHeader(MsgModuleInfoDTO.class));
  132. // 2. 动态字段(直接从dynamicPropertiesList提取code)
  133. if (CollUtil.isNotEmpty(items)) {
  134. MsgModuleInfoDTO firstItem = items.get(0);
  135. MessageTemplateDynamicProperties dynamicProperties = firstItem.getDynamicProperties();
  136. if (dynamicProperties != null && CollUtil.isNotEmpty(dynamicProperties.getDynamicPropertiesList())) {
  137. // 去重处理code,避免重复表头
  138. dynamicProperties.getDynamicPropertiesList().stream()
  139. .map(CodeAndValue::getName)
  140. .distinct()
  141. .forEach(name -> headerMap.putIfAbsent(name, null));
  142. }
  143. }
  144. return headerMap;
  145. }
  146. /**
  147. * 工具方法:获取类中带有@ExcelProperty注解的字段
  148. *
  149. * @param clazz 类
  150. * @return {@link Map }<{@link String }, {@link Field }>
  151. * @author zhumq
  152. * @date 2025/01/22 14:29:55
  153. */
  154. private Map<String, Field> getExcelHeader(Class<?> clazz) {
  155. Map<String, Field> fieldMap = new LinkedHashMap<>();
  156. Field[] fields = clazz.getDeclaredFields();
  157. for (Field field : fields) {
  158. if (field.isAnnotationPresent(ExcelProperty.class)) {
  159. ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
  160. // 获取注解中的字段名称
  161. fieldMap.put(excelProperty.value()[0], field);
  162. }
  163. }
  164. return fieldMap;
  165. }
  166. /**
  167. * 转换字段值为字符串
  168. *
  169. * @param value
  170. * @return {@link Object }
  171. * @author zhumq
  172. * @date 2025/01/22 14:50:16
  173. */
  174. private Object convertValue(Object value) {
  175. if (value instanceof Boolean) {
  176. return (Boolean) value ? "是" : "否";
  177. } else if (value instanceof Integer) {
  178. return (Integer) value == 1 ? "是" : "否";
  179. } else if (value == null) {
  180. return "";
  181. }
  182. return value;
  183. }
复制代码

到此这篇关于Java导出Excel动态表头的示例详解的文章就介绍到这了,更多相关Java导出Excel动态表头内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
15
积分
10
注册时间
2022-12-24
最后登录
2022-12-24

发表于 2025-11-13 18:32:18 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
21
积分
22
注册时间
2022-12-25
最后登录
2022-12-25

发表于 7 天前 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表