1、选中需要动态生成数据的部分,按 Ctrl+F9 -> 选择编辑域。
2.1、选择邮件合并,并在域代码部分后面加上相应参数,例如 :${videoNum} 'videoNum'是对应的参数名,后面代码编辑的时候会用到。
2.2、如果是表格类型的动态数据,按照2.1的方式设置参数,但参数以 ${video.name} 方式设置,用以后续动态数据的生成。
2、代码编写
2.1 pom导入
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls</artifactId>
<version>2.6.0</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-poi</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.core</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
2.2、代码编写
2.2.1、编写控制层
import com.fongtech.cli.report.service.ReportService;
import com.fongtech.cli.report.util.ResUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
* @Author: lin
* @Date: 2022/1/10 14:25
* @Version 1.0
@Slf4j
@RestController
@RequestMapping("/report")
@Api(tags = "报表相关接口")
public class ReportController {
@Autowired
private ReportService reportService;
@GetMapping("/exportReportExcel")
@ApiOperation("导出数据分析报告")
public Object exportReportExcel(HttpServletRequest request,
@RequestParam(value = "startDate", required = false) String startDate,
@RequestParam(value = "endDate", required = false) String endDate,
@RequestParam(value = "type", required = true) String type) {
log.info("数据分析报告导出 开始时间:" + startDate + " 结束时间:" + endDate + " 类型:" + type);
byte[] data = reportService.getWordByte(startDate, endDate, type);
return ResUtil.getStreamData(request, data, "wordReportExport-" + type, "docx");
2.2.2、编写service层
* @Author: linbo
* @Date: 2022/1/10 14:25
* @Version 1.0
public interface ReportService {
* 报告导出
* @param startDate
* @param endDate
* @return
byte[] getWordByte(String startDate,String endDate, String type);
import com.fongtech.cli.report.util.ExportData;
import com.fongtech.cli.report.service.ReportService;
import com.fongtech.cli.report.util.SoMap;
import com.fongtech.cli.report.util.WordUtil;
import com.fongtech.cli.report.vo.ReportVo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.*;
* @Author: lin
* @Date: 2022/1/7 10:13
* @Version 1.0
@Service
public class ReportServiceImpl implements ReportService {
* 模板文件的地址
@Value("${report.model-path-day}")
private String MODEL_PATH_DAY;
@Override
public byte[] getWordByte(String startDate, String endDate, String type) {
ExportData evaluation = WordUtil.createExportData(MODEL_PATH_DAY);
String[] date = getDate(startDate);
evaluation.setData("beginYear", date[0]);
evaluation.setData("beginMonth", date[1]);
evaluation.setData("beginDay", date[2]);
String[] dateEnd = getDate(endDate);
evaluation.setData("endYear", dateEnd[0]);
evaluation.setData("endMonth", dateEnd[1]);
evaluation.setData("endDay", dateEnd[2]);
evaluation.setData("videoNum", "10");
List<SoMap> videoList = new ArrayList<SoMap>();
for (int i = 0; i < 5; i++) {
ReportVo reportVo = new ReportVo();
reportVo.setName("园区名称" + i);
reportVo.setPark("园区" + i);
reportVo.setCondition("设备状态" + i);
videoList.add(new SoMap(reportVo));
evaluation.setTable("video", videoList);
byte[] data = evaluation.getByteArr();
return data;
private String[] getDate(String date) {
if (date.contains(" ")) {
return date.split("-| ");
} else {
return date.split("-");
application.yml文件:
report:
model-path-day: "E://modelDay.docx" #根据模板文件位置设定
2.2.3、Utile类设置
ExportData:
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.images.ByteArrayImageProvider;
import fr.opensagres.xdocreport.document.images.IImageProvider;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import org.springframework.core.io.ClassPathResource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class ExportData {
private IXDocReport report;
private IContext context;
* 构造方法
* @param report
* @param context
public ExportData(IXDocReport report, IContext context) {
this.report = report;
this.context = context;
* 设置普通数据,包括基础数据类型,数组,试题对象
* 使用时,直接 ${key.k} 或者 [#list d as key]
* @param key 健
* @param value 值
public void setData(String key, Object value) {
context.put(key, value);
* 设置表格数据,用来循环生成表格的 List 数据
* 使用时,直接 ${key.k}
* @param key 健
* @param maps List 集合
public void setTable(String key, List<SoMap> maps) {
FieldsMetadata metadata = report.getFieldsMetadata();
metadata = metadata == null ? new FieldsMetadata() : metadata;
SoMap map = maps.get(0);
for (String kk : map.keySet()) {
metadata.addFieldAsList(key + "." + kk);
report.setFieldsMetadata(metadata);
context.put(key, maps);
* 设置图片数据
* 使用时 直接在书签出 key
* @param key 健
* @param url 图片地址
public void setImg(String key, String url) {
FieldsMetadata metadata = report.getFieldsMetadata();
metadata = metadata == null ? new FieldsMetadata() : metadata;
metadata.addFieldAsImage(key);
report.setFieldsMetadata(metadata);
try (
InputStream in = new ClassPathResource(url).getInputStream();
IImageProvider img = new ByteArrayImageProvider(in);
context.put(key, img);
} catch (IOException ex) {
throw new RuntimeException(ex.getMessage());
* 获取文件流数据
* @return 文件流数组
public byte[] getByteArr() {
try (
ByteArrayOutputStream out = new ByteArrayOutputStream();
report.process(context, out);
return out.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex.getMessage());
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
public class ResUtil {
* 生成下载文件,浏览器直接访问为下载文件
* @param request 请求对象
* @param data 数据流数组
* @param prefix 下载的文件名
* @param suffix 文件后缀
* @return 浏览器可以直接下载的文件流
public static ResponseEntity<byte[]> getStreamData(
HttpServletRequest request, byte[] data, String prefix, String suffix
HttpHeaders headers = new HttpHeaders();
prefix = StringUtils.isEmpty(prefix) ? "未命名" : prefix;
suffix = suffix == null ? "" : suffix;
try {
String agent = request.getHeader("USER-AGENT");
boolean isIE = null != agent, isMC = null != agent;
isIE = isIE && (agent.indexOf("MSIE") != -1 || agent.indexOf("Trident") != -1);
isMC = isMC && (agent.indexOf("Mozilla") != -1);
prefix = isMC ? new String(prefix.getBytes("UTF-8"), "iso-8859-1") :
(isIE ? java.net.URLEncoder.encode(prefix, "UTF8") : prefix);
headers.setContentDispositionFormData("attachment", prefix + "." + suffix);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<byte[]>(data, headers, HttpStatus.OK);
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
throw new RuntimeException(ex.getMessage());
package com.fongtech.cli.report.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
* @Author: linbo
* @Date: 2022/1/4 11:38
* @Version 1.0
public class SoMap extends HashMap<String, Object> {
public SoMap() { }
* 构造方法,将任意实体类转化为 Map
* @param obj
public SoMap(Object obj) {
Class clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
try {
for (Field field : fields) {
field.setAccessible(true);
this.put(field.getName(), field.get(obj));
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex.getMessage());
* 将 Map 转化为 任意实体类
* @param clazz 反射获取类字节码对象
* @return
public <T> T toEntity(Class<T> clazz) {
Field[] fields = clazz.getDeclaredFields();
try {
Constructor constructor = clazz.getDeclaredConstructor();
T t = (T) constructor.newInstance();
for (Field field : fields) {
field.setAccessible(true);
field.set(t, this.get(field));
return t;
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage());
* 从集合中获取一个字段的方法,如果字段不存在返回空
* @param key 字段的唯一标识
* @param <T> 字段的类型,运行时自动识别,使用时无需声明和强转
* @return 对应字段的值
public <T> T get(String key) {
return (T) super.get(key);
package com.fongtech.cli.report.util;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import java.io.FileInputStream;
import java.io.InputStream;
public class WordUtil {
* 获取 Word 模板的两个操作对象 IXDocReport 和 IContext
* @param path 模板绝对地址
* @return 模板数据对象
public static ExportData createExportData(String path) {
try {
IXDocReport report = createReport(path);
IContext context = report.createContext();
return new ExportData(report, context);
} catch (XDocReportException ex) {
throw new RuntimeException(ex.getMessage());
* 加载模板的方法,主要是指定模板的路径和选择渲染数据的模板
* @param url 模板相对于类路径的地址
* @return word 文档操作类
private static IXDocReport createReport(String url) {
try (
InputStream in = new FileInputStream(url);
IXDocReport ix = XDocReportRegistry.getRegistry().loadReport(in, TemplateEngineKind.Freemarker);
return ix;
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage());
package com.fongtech.cli.report.vo;
import com.fongtech.cli.framework.model.BaseModel;
import java.io.Serializable;
* @Author: linbo
* @Date: 2022/1/4 13:47
* @Version 1.0
public class ReportVo extends BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String transmissibility;
private String condition;
private String park;
public String getPark() {
return park;
public void setPark(String park) {
this.park = park;
public String getName() {
return name;
public void setName(String name) {
this.name = name;
public String getTransmissibility() {
return transmissibility;
public void setTransmissibility(String transmissibility) {
this.transmissibility = transmissibility;
public String getCondition() {
return condition;
public void setCondition(String condition) {
this.condition = condition;
3、数据导出