Java操作Word文档

柚几哥哥 2024-08-20 16:05:03 阅读 65

文章目录

Java操作Word文档引言1、技术选型结论

2、基础文本填充2.1 引入依赖2.1.1. poi2.1.2. poi-ooxml2.1.3. poi-ooxml-schemas

总结2.2 业务思路2.3 业务层 OfficeService2.4 通用工具类 OfficeUtils2.5 控制层 OfficeController

3、表格3.1 准备模板3.2 业务层 OfficeService业务流程:名词解释:

3.2 Word工具类OfficeUtils3.3 导出效果3.4 动态表格

4、自定义图表4.1 思路4.1.1 概述4.1.2 支持的图表类型4.1.3 特性

4.2 准备模板4.3 导入依赖4.4 图表生成工具类 ChartWithChineseExample步骤 1: 准备字体文件步骤 2: 注册字体到`FontFactory`步骤 3: 设置图表具体位置的字体柱状图:饼图:折线图:

完整代码:

4.5 业务层 OfficeServicel4.6 导出效果

Java操作Word文档

在日常开发中,经常遇到需要自动化处理Word文档的需求,比如批量生成报告、填写模板内容等。Java作为一种广泛应用的编程语言,提供了多种方式来操作Word文档。本文将详细介绍如何使用Java处理Word文档,并通过实战示例带你入门。

引言

Word文档本质上是一个遵循Open XML标准的ZIP压缩包,包含了一系列XML文件和其他资源(如图片)。因此,操作Word文档的关键在于解析和修改这些XML文件。Java开发者可以选择多种库来实现这一目标,包括但不限于Apache POI、docx4j、iText以及Spire.Doc for Java等。下面,我们将逐一探讨这些工具,并给出具体示例。

1、技术选型

工具 优点 缺点 简介
Apache POI 开源免费、社区活跃、功能完善。 对于复杂的Word样式处理支持有限。 Apache POI是Apache软件基金会的一个项目,提供了一套用于读写Microsoft Office格式档案的Java API,包括Word、Excel等。对于Word文档,主要使用的是POI的HWPF(处理<code>.doc文件)和XWPF(处理.docx文件)模块。
docx4j 功能强大,支持复杂Word操作,如样式、表格、图片插入等。 学习曲线相对较陡峭,文档相对不够丰富。 docx4j是一个开源库,专为操作.docx(Open XML)格式的Word文档设计,提供了丰富的API来处理XML内容。
iText 如果你的项目已经使用了iText处理PDF,那么使用它来生成简单的Word文档会比较方便。 Word处理功能不如Apache POI或docx4j全面。 虽然iText主要用于PDF处理,但它也支持生成Word(.docx)文档,尽管功能相比专门的Word处理库较为有限。
Spire.Doc for Java 功能强大,支持度高,文档和客户服务较完善。 需要付费使用,免费版有功能限制。 Spire.Doc for Java是一个商业库,专注于Word文档的处理,提供了丰富的功能,包括创建、读取、编辑、转换Word文档等。

结论

选择合适的库取决于你的具体需求和项目条件。如果你需要处理大量复杂的Word文档且预算允许,Spire.Doc可能是最佳选择。而对于开源解决方案,Apache POI适合初学者和基本需求,而docx4j则更适合处理高级场景。iText虽能生成Word,但更擅长PDF处理。无论哪种选择,掌握基本的API使用和理解Word的内部结构都是关键。希望本文能帮助你在Java项目中有效操作Word文档。

本篇文章主要以Apache POI来实现具体业务。

2、基础文本填充

2.1 引入依赖

2.1.1. poi

基础库poi是最基础的Apache POI库,包含了处理老版本Office文件格式(如.xls.doc)的类和方法。它不直接支持.xlsx.docx等基于XML的文件格式。这个库主要用于处理二进制文件格式,并且是其他更特定库的基础。

2.1.2. poi-ooxml

XML支持poi-ooxml是针对基于XML的Office Open XML格式(.xlsx.docx.pptx等)的扩展库。它依赖于poi库,并添加了处理Open XML文件所需的所有额外类和方法。当你需要读写新格式的Office文件时,这个库是必不可少的。它包含了解析和生成Open XML文档所需的API。

2.1.3. poi-ooxml-schemas

XML模式与验证poi-ooxml-schemas包含了Office Open XML格式的完整XML模式定义。这些模式定义对于验证生成的Open XML文档是否符合官方规范非常重要,确保了文档的兼容性和正确性。这个依赖项不是直接用于编写代码操作POI的API,而是作为后台支持,帮助POI库正确解析和验证XML结构。

总结

如果你只处理老版本的Office文件(.xls, .doc),可能只需要poi库。处理.xlsx, .docx等XML格式的文件时,你需要同时引入poipoi-ooxml,因为后者依赖前者,并且提供了处理这些新格式的功能。poi-ooxml-schemas虽然不是每次都需要,但对于确保生成的文档结构正确,特别是在复杂的文档处理场景下,是非常推荐加入的依赖,因为它提供了详细的XML模式验证能力。

在Maven或Gradle项目中,通常你会同时声明这三个依赖(如果处理XML格式文件的话),以确保所有必要的组件都已就绪。

<properties>

<poi-ooxml.version>4.1.2</poi-ooxml.version>

<poi.version>3.17</poi.version>

</properties>

<dependencies>

<!-- poi -->

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi</artifactId>

<version>${poi.version}</version>

</dependency>

<!-- poi-ooxml -->

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-ooxml</artifactId>

<version>${poi.version}</version>

</dependency>

<!-- 读写Microsoft Office poi-ooxml-schemas -->

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-ooxml-schemas</artifactId>

<version>${poi-ooxml.version}</version>

</dependency>

</dependencies>

2.2 业务思路

报告模板设计

使用Word文档作为模板,预先设计好报告的布局,包括封面页、基本信息页、测量数据表格、历史数据图表等部分。在需要填充数据的地方,设定占位符或者使用特定标记(例如{ {username}}{ {weight}}等)。

使用Apache POI生成Word文档

利用Apache POI库(特别是poipoi-ooxml)来读取模板Word文件,并根据整理好的数据集替换模板中的占位符:

加载模板文档。

遍历文档,查找并替换所有的占位符。

利用poi-ooxml和图表生成库(如JFreeChart结合Apache POI导出图表)生成历史测量数据的柱状图,并嵌入Word文档中。

文件存储与接口设计

生成的Word文档可以临时保存在服务器的文件系统或云存储中。设计一个RESTful API,接收生成报告的请求,处理逻辑后,返回文件的下载链接或Base64编码的文件内容给前端。考虑到安全性,可以设置链接的有效期,过期自动删除临时文件。

在这里插入图片描述

将模板文件放置resourcs/templates文件夹下,

在这里插入图片描述

2.3 业务层 OfficeService

<code>package com.example.demo.service.impl;

import com.example.demo.dto.HealthReportQuery;

import com.example.demo.service.OfficeService;

import com.example.demo.uitls.Office2PdfService;

import com.example.demo.uitls.OfficeUtils;

import com.example.demo.uitls.SpringUtils;

import jakarta.annotation.Resource;

import jakarta.servlet.http.HttpServletResponse;

import org.springframework.core.io.ResourceLoader;

import org.springframework.stereotype.Service;

import java.io.FileInputStream;

import org.apache.poi.xwpf.usermodel.XWPFDocument;

/**

* OfficeServiceImpl :

*

* @author zyw

* @create 2024-06-24 15:41

*/

@Service

public class OfficeServiceImpl implements OfficeService {

/**

* 个人健康报告模板

*/

public static final String PERSONAL_HEALTH_REPORT_TEMPLATE = "classpath:templates/HealthReport.docx";

@Resource

private ResourceLoader resourceLoader;

@Resource

private Office2PdfService office2PdfService;

@Override

public XWPFDocument getHealthReport(HealthReportQuery query) {

try {

FileInputStream fileInputStream = SpringUtils.convertInputStreamToFileInputStream(resourceLoader.getResource(PERSONAL_HEALTH_REPORT_TEMPLATE).getInputStream());

XWPFDocument xwpfDocument = new XWPFDocument(fileInputStream);

// 替换文本数据构建

OfficeUtils.paragraphTextFilling(xwpfDocument,OfficeUtils.objectToMap(query));

return xwpfDocument;

} catch (Exception e) {

return null;

}

}

@Override

public void getHealthReportWord(XWPFDocument document, HealthReportQuery query, HttpServletResponse response) {

OfficeUtils.processingWordResponses("健康问卷-" + query.getName(), OfficeUtils.writeDocumentToInputStream(document), response);

}

}

这里用到了SpringUtils 中的convertInputStreamToFileInputStream 方法来将模板文件从对应路径中读取

SpringUtils

package com.example.demo.uitls;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

import org.springframework.core.io.Resource;

import org.springframework.stereotype.Component;

import java.io.*;

/**

* @author zyw

*/

@Component

public class SpringUtils implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

if (SpringUtils.applicationContext == null) {

SpringUtils.applicationContext = applicationContext;

}

}

public static Object getBean(String name) {

return applicationContext.getBean(name);

}

public static <T> T getBean(Class<T> requiredType) {

return applicationContext.getBean(requiredType);

}

public static InputStream readResourceFile(String path) throws IOException {

Resource resource = applicationContext.getResource(path);

InputStream inputStream = resource.getInputStream();

return inputStream;

}

public static File getResourceFile(String path) throws IOException {

Resource resource = applicationContext.getResource(path);

return resource.getFile();

}

/**

* 将 InputStream 转换为 FileInputStream

* @param inputStream

* @return

* @throws IOException

*/

public static FileInputStream convertInputStreamToFileInputStream(InputStream inputStream) throws IOException {

// 从 inputStream 创建一个临时文件

File tempFile = File.createTempFile("temp", ".tmp");

tempFile.deleteOnExit(); // 确保程序退出时删除临时文件

// 将 inputStream 写入临时文件

try (FileOutputStream out = new FileOutputStream(tempFile)) {

byte[] buffer = new byte[1024];

int bytesRead;

while ((bytesRead = inputStream.read(buffer)) != -1) {

out.write(buffer, 0, bytesRead);

}

}

// 返回新的 FileInputStream 对象,从临时文件中读取数据

return new FileInputStream(tempFile);

}

}

2.4 通用工具类 OfficeUtils

package com.example.demo.uitls;

import jakarta.servlet.http.HttpServletResponse;

import org.apache.poi.xwpf.usermodel.XWPFDocument;

import org.apache.poi.xwpf.usermodel.XWPFParagraph;

import org.apache.poi.xwpf.usermodel.XWPFRun;

import java.lang.reflect.Field;

import java.io.*;

import java.util.*;

/**

* OfficeUtils : Office工具类

*

* @author zyw

* @create 2024-06-24 16:35

*/

public class OfficeUtils {

/**

* 对象转Map

* @param obj

* @return

*/

public static Map<String, String> objectToMap(Object obj) {

Map<String, String> map = new HashMap<>();

Class<?> clazz = obj.getClass();

// 获取类中所有声明的字段(包括私有、受保护、默认、公共)

Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {

field.setAccessible(true); // 设置字段可访问(如果是私有的)

try {

Object value = field.get(obj);

String key = "${" + field.getName() + "}"; // 构造key,以${name}形式

map.put(key, String.valueOf(value));

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

return map;

}

/**

* 段落文本填充

*

* @param document 文档

* @param insertTextMap 填充内容

*/

public static void paragraphTextFilling(XWPFDocument document, Map<String, String> insertTextMap) {

Set<String> set = insertTextMap.keySet();

Iterator<XWPFParagraph> itPara = document.getParagraphsIterator();

while (itPara.hasNext()) {

// 获取文档中当前的段落文字信息

XWPFParagraph paragraph = itPara.next();

List<XWPFRun> run = paragraph.getRuns();

// 遍历段落文字对象

for (int i = 0; i < run.size(); i++) {

// 获取段落对象

if (run.get(i) == null) { //段落为空跳过

continue;

}

String sectionItem = null;

try {

// 检查段落中是否包含文本框

sectionItem = run.get(i).getText(run.get(i).getTextPosition()); //段落内容

} catch (Exception e) {

}

if (sectionItem == null) {

continue;

}

// 遍历自定义表单关键字,替换Word文档中的内容

Iterator<String> iterator = set.iterator();

while (iterator.hasNext()) {

// 当前关键字

String key = iterator.next();

// 替换内容

sectionItem = sectionItem.replace(key, String.valueOf(insertTextMap.get(key)));

}

run.get(i).setText(sectionItem, 0);

}

}

}

/**

* 处理Word响应

*

* @param downloadName 下载文件名

* @param inputStream 文件输入流

* @param response 响应

*/

public static void processingWordResponses(String downloadName,

InputStream inputStream,

HttpServletResponse response) {

try {

// 设置响应的Content-Type

response.setContentType("application/octet-stream");

response.setCharacterEncoding("utf-8");

// 设置Content-Disposition头部,指示浏览器下载文件,文件名为document.docx

downloadName = new String(downloadName.getBytes("UTF-8"), "ISO-8859-1");

response.setHeader("Content-Disposition", "attachment;filename=" + downloadName + ".docx");code>

// 获取响应的输出流

OutputStream outputStream = response.getOutputStream();

byte[] buffer = new byte[4096];

int bytesRead = -1;

// 将InputStream中的内容写入到OutputStream中

while ((bytesRead = inputStream.read(buffer)) != -1) {

outputStream.write(buffer, 0, bytesRead);

}

// 关闭流

inputStream.close();

outputStream.close();

}catch (Exception e){

}

}

/**

* word转InputStream

*

* @param document

* @return

*/

public static InputStream writeDocumentToInputStream(XWPFDocument document) {

try {

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

document.write(byteArrayOutputStream);

byteArrayOutputStream.close();

return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());

} catch (IOException e) {

e.printStackTrace();

return null;

}

}

}

2.5 控制层 OfficeController

package com.example.demo.controller;

import com.example.demo.dto.HealthReportQuery;

import com.example.demo.service.OfficeService;

import io.swagger.v3.oas.annotations.Operation;

import io.swagger.v3.oas.annotations.Parameter;

import io.swagger.v3.oas.annotations.Parameters;

import io.swagger.v3.oas.annotations.enums.ParameterIn;

import io.swagger.v3.oas.annotations.tags.Tag;

import jakarta.annotation.Resource;

import jakarta.servlet.http.HttpServletResponse;

import org.springframework.web.bind.annotation.*;

/**

* OfficeController : Office办公文件控制器

*

* @author zyw

* @create 2024-06-24 15:40

*/

@Tag(name = "Office办公文件控制器")

@RestController

@RequestMapping("/office")

public class OfficeController {

@Resource

private OfficeService officeService;

@GetMapping("/getHealthReportWord")

@Operation(summary = "获取健康报告Word", description = "获取健康报告")

@Parameters({

@Parameter(name = "name", description = "姓名", required = true, in = ParameterIn.QUERY),

@Parameter(name = "gender", description = "性别", required = true, in = ParameterIn.QUERY),

@Parameter(name = "age", description = "年龄", required = true, in = ParameterIn.QUERY)

})

public void getHealthReportWord(HealthReportQuery query, HttpServletResponse response) {

officeService.getHealthReportWord(officeService.getHealthReport(query), query, response);

}

}

在这里插入图片描述

可以看到我们通过接口传输的三个参数均已渲染到了指定${}位置

在这里插入图片描述

3、表格

需求:我们需要根据输入的身高、体重、运动能力在文档中动态展示所处的健康状态

3.1 准备模板

在这里插入图片描述

在基本信息中我们需要将姓名、性别、头像等基本信息填入模板中已存在的表格的指定单元格里在"3、您目前的体育运动水平"和"4、您的体重指数"两个标题下我们需要动态生成"体力活动水平标尺"和"体重指数标尺",同时展示出所处的健康状态

3.2 业务层 OfficeService

业务流程:

模板中已存在的表格,可以通过遍历文档所有表格获取:List tables = document.getTables();

模板中未存在的表格,我们通过找到模板中所需插入动态表格的上一个段落,再其下创建新的段落以及表格实现;

名词解释:

XWPFDocument: 这个类代表一个Word文档。它是操作Word文件的入口点,允许你创建新的文档,读取现有的文档,添加或删除段落、表格、图片等元素。XWPFTable: 表示Word文档中的表格。你可以使用这个类来创建新的表格,获取或设置表格的属性(比如宽度、边框样式),以及操作表格中的行和单元格。XWPFParagraph: 代表文档中的一个段落。段落可以包含文本、图片、表格等多种元素。你可以使用这个类来创建新的段落,设置对齐方式、缩进、间距等格式,以及添加或删除段落中的文本或其它内容。XWPFTableCell: 单元格类,表示表格中的一个单元格。你可以通过这个类来设置单元格的内容(包括文本和嵌入的对象)、样式(如背景色、边框)以及合并或拆分单元格等。XWPFRun: 运行对象,是段落中最基本的文本处理单位。一个段落可以由一个或多个run组成,每个run可以有不同的字体样式、颜色、大小等。当你需要在同一个段落中应用不同的格式时,就会用到多个run。例如,改变文本颜色、加粗或斜体等操作都是通过对特定的run进行设置来实现的。

综上所述,这些类共同构成了操作Word文档的框架,让你能够在Java程序中灵活地创建和修改复杂的Word文档结构。

<code>package com.example.demo.service.impl;

import com.example.demo.dto.HealthReportQuery;

import com.example.demo.service.OfficeService;

import com.example.demo.uitls.Office2PdfService;

import com.example.demo.uitls.OfficeUtils;

import com.example.demo.uitls.SpringUtils;

import jakarta.annotation.Resource;

import jakarta.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang.StringUtils;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;

import org.apache.poi.util.Units;

import org.apache.poi.xwpf.usermodel.*;

import org.apache.xmlbeans.XmlCursor;

import org.springframework.core.io.ResourceLoader;

import org.springframework.stereotype.Service;

import java.io.*;

import java.net.URL;

import java.text.DecimalFormat;

import java.util.List;

import java.util.Objects;

/**

* OfficeServiceImpl :

*

* @author zyw

* @create 2024-06-24 15:41

*/

@Service

@Slf4j

public class OfficeServiceImpl implements OfficeService {

/**

* 个人健康报告模板

*/

public static final String PERSONAL_HEALTH_REPORT_TEMPLATE = "classpath:templates/HealthReport.docx";

private static final String HEADER_1_3 = "3、您目前的体育运动水平";

private static final String HEADER_1_4 = "4、您的体重指数";

@Resource

private ResourceLoader resourceLoader;

@Resource

private Office2PdfService office2PdfService;

@Override

public XWPFDocument getHealthReport(HealthReportQuery query) {

try {

FileInputStream fileInputStream = SpringUtils.convertInputStreamToFileInputStream(resourceLoader.getResource(PERSONAL_HEALTH_REPORT_TEMPLATE).getInputStream());

XWPFDocument xwpfDocument = new XWPFDocument(fileInputStream);

// 替换文本数据构建

OfficeUtils.paragraphTextFilling(xwpfDocument, OfficeUtils.objectToMap(query));

// 在基本信息表格中填充数据

fillInTable(xwpfDocument, query);

// 插入体育运动水平表格

int index3 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_1_3);

handleTableOne(xwpfDocument, index3, query.getSportsLevel());

// 插入体重指数表格

int index4 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_1_4);

handleTableTwo(xwpfDocument, index4, query.getHeight(), query.getWeight());

return xwpfDocument;

} catch (Exception e) {

return null;

}

}

/**

* 填充基本信息表格

*

* @param document

* @param query

*/

public void fillInTable(XWPFDocument document, HealthReportQuery query) throws IOException, InvalidFormatException {

// 获取表格对象集合

List<XWPFTable> tables = document.getTables();

// 获取模板中第一个表格

XWPFTable xwpfTable = tables.get(0);

xwpfTable.getRow(0).getCell(1).setText(query.getName());

xwpfTable.getRow(0).getCell(3).setText(query.getGender());

xwpfTable.getRow(1).getCell(1).setText(query.getAge() + "岁");

xwpfTable.getRow(1).getCell(3).setText(query.getNativePlace());

xwpfTable.getRow(2).getCell(1).setText(query.getHeight() + "cm");

xwpfTable.getRow(2).getCell(3).setText(query.getWeight() + "kg");

xwpfTable.getRow(3).getCell(1).setText(String.valueOf(query.getPhone()));

xwpfTable.getRow(4).getCell(1).setText(query.getAddress());

// 在第一行第五列插入图片

XWPFTableCell cell04 = xwpfTable.getRow(0).getCell(4);

XWPFParagraph xwpfParagraph = cell04.getParagraphs().get(0);

// 通过URL获取图片数据

InputStream inputStream = new URL("http://127.0.0.1:1030/zyw/static/2024/06/25/lbxx_20240625141543A001.png").openStream();

XWPFRun run = xwpfParagraph.createRun();

run.addPicture(inputStream,

Document.PICTURE_TYPE_PNG, "头像",

Units.toEMU(150), Units.toEMU(150));

// 设置垂直居中

cell04.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);

for (XWPFParagraph para : cell04.getParagraphs()) {

//居中

para.setAlignment(ParagraphAlignment.CENTER);

}

inputStream.close();

}

/**

* 表格1 (体育运动水平)

*

* @param document 文档

* @param index 索引

* @param sportsLevel 个人运动水平

*/

public void handleTableOne(XWPFDocument document, Integer index, String sportsLevel) {

// 获取所有段落

List<XWPFParagraph> paragraphs = document.getParagraphs();

// 在目标段落后添加一个新的段落

XWPFParagraph paragraph = document.insertNewParagraph(paragraphs.get(index + 1).getCTP().newCursor().newCursor());

// 设置段落的样式和属性,实现换行

paragraph.setWordWrap(true); // 设置自动换行

// 创建表格

XmlCursor cursor = paragraph.getCTP().newCursor();

// 在指定游标位置插入表格

XWPFTable table = document.insertNewTbl(cursor);

// 去除表格边框设置表格宽度

OfficeUtils.setTableWidthToRemoveBorder(table, 7920);

// 设置表格内容

XWPFTableRow row0 = OfficeUtils.createRow(table, 0);

OfficeUtils.setRowHeight(row0, 2);

XWPFTableCell cell01 = OfficeUtils.createCell(row0, 0);

XWPFRun run1 = cell01.getParagraphs().get(0).createRun();

// 设置字体为宋体

run1.setFontFamily("宋体");

// 设置字号为四号(12磅)

run1.setFontSize(12);

run1.setText("您目前的体力活动:");

OfficeUtils.setTheLandscapeHeader(cell01, 0.25);

for (int i = 1; i <= 3; i++) {

OfficeUtils.setsTheCellWidth(OfficeUtils.createCell(row0, i), 0.13);

}

if (StringUtils.isNotBlank(sportsLevel)) {

XWPFTableCell cell;

switch (sportsLevel) {

case "体力活动不足":

cell = row0.getCell(1);

cell.setText("不足");

//大红色

cell.setColor("FF0000");

break;

case "体力活动中等":

cell = row0.getCell(2);

cell.setText("中等");

//天蓝色

cell.setColor("4E95D9");

break;

case "体力活动充分":

cell = row0.getCell(3);

cell.setText("充分");

//绿色

cell.setColor("00FF00");

break;

default:

}

} else {

XWPFTableCell cell = row0.getCell(1);

cell.setText("暂无分析数据");

}

// 在目标段落后添加第二个新的段落

XWPFParagraph paragraph2 = document.insertNewParagraph(paragraphs.get(index + 2).getCTP().newCursor().newCursor());

// 设置段落的样式和属性,实现换行

paragraph2.setWordWrap(true); // 设置自动换行

// 创建表格

XmlCursor cursor2 = paragraph2.getCTP().newCursor();

// 在指定游标位置插入表格

XWPFTable table2 = document.insertNewTbl(cursor2);

// 去除表格边框设置表格宽度

OfficeUtils.setTableWidthToRemoveBorder(table2, 7920);

XWPFTableRow row1 = OfficeUtils.createRow(table2, 0);

OfficeUtils.setRowHeight(row1, 2);

XWPFTableCell cell11 = OfficeUtils.createCell(row1, 0);

XWPFRun run2 = cell11.getParagraphs().get(0).createRun();

// 设置字体为宋体

run2.setFontFamily("宋体");

// 设置字号为四号(12磅)

run2.setFontSize(12);

run2.setText("体力活动水平标尺:");

OfficeUtils.setTheLandscapeHeader(cell11, 0.25);

XWPFTableCell cell12 = OfficeUtils.createCell(row1, 1);

cell12.setText("不足");

OfficeUtils.setsTheCellWidth(cell12, 0.13);

//大红色

cell12.setColor("FF0000");

XWPFTableCell cell13 = OfficeUtils.createCell(row1, 2);

cell13.setText("中等");

OfficeUtils.setsTheCellWidth(cell13, 0.13);

//天蓝色

cell13.setColor("4E95D9");

XWPFTableCell cell14 = OfficeUtils.createCell(row1, 3);

cell14.setText("充分");

OfficeUtils.setsTheCellWidth(cell14, 0.13);

//绿色

cell14.setColor("00B050");

}

/**

* 表格2

*

* @param document 文档

* @param index 索引

* @param height 身高

* @param weight 体重

*/

public void handleTableTwo(XWPFDocument document, Integer index, Double height, Double weight) {

// 获取所有段落

List<XWPFParagraph> paragraphs = document.getParagraphs();

// 在目标段落后添加一个新的段落

XWPFParagraph paragraph1 = document.insertNewParagraph(paragraphs.get(index + 1).getCTP().newCursor().newCursor());

// 设置段落的样式和属性,实现换行

paragraph1.setWordWrap(true); // 设置自动换行

// 创建表格

XmlCursor cursor1 = paragraph1.getCTP().newCursor();

// 在指定游标位置插入表格

XWPFTable table1 = document.insertNewTbl(cursor1);

// 去除表格边框设置表格宽度

OfficeUtils.setTableWidthToRemoveBorder(table1, 7920);

// 设置表格内容

XWPFTableRow row00 = OfficeUtils.createRow(table1, 0);

OfficeUtils.setRowHeight(row00, 2);

XWPFTableCell cell001 = OfficeUtils.createCell(row00, 0);

XWPFRun run01 = cell001.getParagraphs().get(0).createRun();

// 设置字体为宋体

run01.setFontFamily("宋体");

// 设置字号为四号(12磅)

run01.setFontSize(12);

run01.setText("您的体重:");

OfficeUtils.setTheLandscapeHeader(cell001, 0.15);

for (int i = 1; i <= 4; i++) {

OfficeUtils.setsTheCellWidth(OfficeUtils.createCell(row00, i), 0.13);

}

XWPFTableCell cell011 = row00.getCell(1);

if (Objects.nonNull(weight)) {

cell011.setText(weight + "kg");

} else {

cell011.setText("暂未获得");

}

// 在目标段落后添加第二个新的段落

XWPFParagraph paragraph = document.insertNewParagraph(paragraphs.get(index + 2).getCTP().newCursor().newCursor());

// 在目标段落后添加新的段落

// 设置段落的样式和属性,实现换行

paragraph.setWordWrap(true); // 设置自动换行

// 创建表格

XmlCursor cursor = paragraph.getCTP().newCursor();

// 在指定游标位置插入表格

XWPFTable table = document.insertNewTbl(cursor);

// 去除表格边框设置表格宽度

OfficeUtils.setTableWidthToRemoveBorder(table, 7920);

// 设置表格内容

XWPFTableRow row0 = OfficeUtils.createRow(table, 0);

OfficeUtils.setRowHeight(row0, 2);

XWPFTableCell cell01 = OfficeUtils.createCell(row0, 0);

XWPFRun run1 = cell01.getParagraphs().get(0).createRun();

// 设置字体为宋体

run1.setFontFamily("宋体");

// 设置字号为四号(12磅)

run1.setFontSize(12);

run1.setText("您的体重指数:");

OfficeUtils.setTheLandscapeHeader(cell01, 0.15);

for (int i = 1; i <= 4; i++) {

OfficeUtils.setsTheCellWidth(OfficeUtils.createCell(row0, i), 0.13);

}

Double bmi = calculateBmi(height, weight);

if (Objects.nonNull(bmi)) {

XWPFTableCell cell = null;

if (bmi <= 18.5) {

cell = row0.getCell(1);

//天蓝色

cell.setColor("4E95D9");

} else if (bmi <= 24) {

cell = row0.getCell(2);

//绿色

cell.setColor("00B050");

} else if (bmi <= 28) {

cell = row0.getCell(3);

//黄色

cell.setColor("FFC000");

} else {

cell = row0.getCell(4);

//大红色

cell.setColor("FF0000");

}

cell.setText(String.valueOf(bmi));

}

// 在目标段落后添加第三个新的段落

XWPFParagraph paragraph2 = document.insertNewParagraph(paragraphs.get(index + 3).getCTP().newCursor().newCursor());

// 设置段落的样式和属性,实现换行

paragraph2.setWordWrap(true); // 设置自动换行

// 创建表格

XmlCursor cursor2 = paragraph2.getCTP().newCursor();

// 在指定游标位置插入表格

XWPFTable table2 = document.insertNewTbl(cursor2);

// 去除表格边框设置表格宽度

OfficeUtils.setTableWidthToRemoveBorder(table2, 7920);

XWPFTableRow row1 = OfficeUtils.createRow(table2, 0);

OfficeUtils.setRowHeight(row1, 2);

XWPFTableCell cell11 = OfficeUtils.createCell(row1, 0);

XWPFRun run2 = cell11.getParagraphs().get(0).createRun();

// 设置字体为宋体

run2.setFontFamily("宋体");

// 设置字号为四号(12磅)

run2.setFontSize(12);

run2.setText("体重指数标尺:");

OfficeUtils.setTheLandscapeHeader(cell11, 0.15);

XWPFTableCell cell12 = OfficeUtils.createCell(row1, 1);

cell12.setText("0~18.5");

OfficeUtils.setsTheCellWidth(cell12, 0.13);

//天蓝色

cell12.setColor("4E95D9");

XWPFTableCell cell13 = OfficeUtils.createCell(row1, 2);

cell13.setText("18.6~24");

OfficeUtils.setsTheCellWidth(cell13, 0.13);

//绿色

cell13.setColor("00B050");

XWPFTableCell cell14 = OfficeUtils.createCell(row1, 3);

cell14.setText("24.1~28");

OfficeUtils.setsTheCellWidth(cell14, 0.13);

//黄色

cell14.setColor("FFC000");

XWPFTableCell cell15 = OfficeUtils.createCell(row1, 4);

cell15.setText("<28");

OfficeUtils.setsTheCellWidth(cell15, 0.13);

//大红色

cell15.setColor("FF0000");

}

/**

* 计算BMI

*

* @param height 身高

* @param weight 体重

* @return

*/

public Double calculateBmi(Double height, Double weight) {

height = height / 100;

if (height <= 0 || weight <= 0) {

log.error("身高和体重必须是正数!");

return null;

}

DecimalFormat df = new DecimalFormat("#.##");

return Double.parseDouble(df.format(weight / (height * height)));

}

@Override

public void getHealthReportWord(XWPFDocument document, HealthReportQuery query, HttpServletResponse response) {

OfficeUtils.processingWordResponses("健康报告-" + query.getName(), OfficeUtils.writeDocumentToInputStream(document), response);

}

}

图片这里我使用的是在本地文件服务上上传的一张图片,关于文件服务的搭建可以阅读下面这篇博客:

Java实现对象存储的4种方式(本地对象存储、MINIO、阿里云OSS、FastDFS)

3.2 Word工具类OfficeUtils

抽出公共部分代码,编写静态方法到工具类中,解耦合。

这里仅列出这部分功能涉及的静态方法,上诉功能已列出的方法这里不展示。

package com.example.demo.uitls;

import jakarta.servlet.http.HttpServletResponse;

import org.apache.poi.xwpf.usermodel.*;

import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.lang.reflect.Field;

import java.io.*;

import java.math.BigInteger;

import java.util.*;

/**

* OfficeUtils : Office工具类

*

* @author zyw

* @create 2024-06-24 16:35

*/

public class OfficeUtils {

/**

* 设置表格宽度去除边框

* @param table 表格

* @param width 宽度值

*/

public static void setTableWidthToRemoveBorder(XWPFTable table,Integer width) {

// 去除表格边框

CTTblPr tblPr2 = table.getCTTbl().getTblPr();

CTTblBorders borders2 = tblPr2.addNewTblBorders();

borders2.addNewBottom().setVal(STBorder.NONE);

borders2.addNewTop().setVal(STBorder.NONE);

borders2.addNewLeft().setVal(STBorder.NONE);

borders2.addNewRight().setVal(STBorder.NONE);

borders2.addNewInsideH().setVal(STBorder.NONE);

borders2.addNewInsideV().setVal(STBorder.NONE);

// 设置表格整体样式

tblPr2.addNewTblW().setW(BigInteger.valueOf(width)); // 设置表格宽度

}

/**

* 设置表格单元格宽度及文本居中

*

* @param cell 单元格

* @param width 宽度占比

*/

public static void setTheLandscapeHeader(XWPFTableCell cell, double width) {

setsTheCellWidth(cell, width);

// 获取单元格属性对象

CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr();

// 设置垂直对齐方式为居中

CTVerticalJc vJc = tcPr.isSetVAlign() ? tcPr.getVAlign() : tcPr.addNewVAlign();

vJc.setVal(STVerticalJc.CENTER);

}

/**

* 设置表格单元格宽度

*

* @param cell 单元格

* @param width 宽度占比

*/

public static void setsTheCellWidth(XWPFTableCell cell, double width) {

// 假设A4纸宽约为210mm,1mm=360EMU,则A4宽约为7920EMU

int emuFor30Percent = (int) (7920 * width);

CTTblWidth ctTblWidth = cell.getCTTc().addNewTcPr().addNewTcW();

// 设置宽度为2000EMU,你可以根据需要调整这个值

ctTblWidth.setW(BigInteger.valueOf(emuFor30Percent));

// 设置宽度类型为字符单位(也可以是其他单位,如百分比等)

ctTblWidth.setType(STTblWidth.PCT);

// 设置垂直居中

cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);

for (XWPFParagraph para : cell.getParagraphs()) {

//居中

para.setAlignment(ParagraphAlignment.CENTER);

}

}

/**

* 设置表格行的高度

*

* @param row 行

* @param heightCm 高度占比

*/

public static void setRowHeight(XWPFTableRow row, double heightCm) {

int emuForHeight = (int) (360 * heightCm);

CTTrPr trPr = row.getCtRow().addNewTrPr();

CTHeight ht = trPr.addNewTrHeight();

ht.setVal(BigInteger.valueOf(emuForHeight));

}

/**

* 创建表格行

*

* @param table 表格

* @param index 行索引

* @return

*/

public static XWPFTableRow createRow(XWPFTable table, int index) {

return Objects.isNull(table.getRow(index)) ? table.createRow() : table.getRow(index);

}

/**

* 创建单元格

*

* @param row 行

* @param index 列索引

* @return

*/

public static XWPFTableCell createCell(XWPFTableRow row, int index) {

return Objects.isNull(row.getCell(index)) ? row.createCell() : row.getCell(index);

}

/**

* 获取文本在文档中的索引

*

* @param doc 文档

* @param text 文本标识

* @return

*/

public static int findParagraphIndexByText(XWPFDocument doc, String text) {

// 获取所有段落

List<XWPFParagraph> paragraphs = doc.getParagraphs();

// 查找目标段落

int targetParagraphIndex = -1;

for (int i = 0; i < paragraphs.size(); i++) {

if (paragraphs.get(i).getText().contains(text)) {

targetParagraphIndex = i;

break;

}

}

return targetParagraphIndex;

}

}

3.3 导出效果

Word效果:

在这里插入图片描述

PDF效果:

在这里插入图片描述

3.4 动态表格

<code> /**

* 个人健康报告模板

*/

public static final String PERSONAL_HEALTH_REPORT_TEMPLATE = "classpath:templates/HealthReport.docx";

private static final Pattern pattern = Pattern.compile("(\\d+、[^\\d]+)");

private static final String HEADER_1_1 = "您的基本信息";

private static final String HEADER_1_3 = "您目前的体育运动水平";

private static final String HEADER_1_4 = "您的体重指数";

private static final String HEADER_2_1 = "营养成分摄入比例";

private static final String HEADER_2_2 = "心率血氧检查";

private static final String HEADER_2_3 = "睡眠质量趋势";

private static final String HEADER_3_1 = "专家建议";

@Override

public XWPFDocument getHealthReport(HealthReportQuery query) {

try {

FileInputStream fileInputStream = SpringUtils.convertInputStreamToFileInputStream(resourceLoader.getResource(PERSONAL_HEALTH_REPORT_TEMPLATE).getInputStream());

XWPFDocument xwpfDocument = new XWPFDocument(fileInputStream);

// 替换文本数据构建

OfficeUtils.paragraphTextFilling(xwpfDocument, OfficeUtils.objectToMap(query));

// 在基本信息表格中填充数据

fillInTable(xwpfDocument, query);

// 插入体育运动水平表格

int index3 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_1_3);

handleTableOne(xwpfDocument, index3, query.getSportsLevel());

// 插入体重指数表格

int index4 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_1_4);

handleTableTwo(xwpfDocument, index4, query.getHeight(), query.getWeight());

// 插入历史体重

int index5 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_2_1);

insertChartOne(xwpfDocument, index5);

// 插入心率检查

int index6 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_2_2);

insertChartTwo(xwpfDocument, index6);

// 插入睡眠质量趋势

int index7 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_2_3);

insertChartThree(xwpfDocument, index7);

// 插入体格检查动态列表

int index8 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_3_1);

dynamicList(xwpfDocument, index8);

return xwpfDocument;

} catch (Exception e) {

log.info("获取健康报告失败", e);

return null;

}

}

/**

* 动态列表 专家建议

*

* @param document 文档

* @param index 索引

*/

public void dynamicList(XWPFDocument document, Integer index) {

// 数据源

Map<String, String> map1 = new HashMap<>();

map1.put("code", "⽢油三酯增⾼");

map1.put("content", "1、建议限酒,低脂、低胆固醇饮⻝,如少吃油腻及煎烤类⻝物,少吃动物内脏等,多⻝蔬菜⽔果。加强运动,促进脂质代谢。2、每三-六个⽉复查⾎脂和肝脏B超⼀次,复查前请低脂饮⻝三天。如⾎脂持续增⾼,请在医⽣指导下使⽤调脂药物。");

Map<String, String> map2 = new HashMap<>();

map2.put("code", "肌酐增⾼");

map2.put("content", "1、肌酐是临床常规肾功能试验之⼀。肌酐是肌酸的代谢产物,98%的肌酸存在于肌⾁,为肌⾁收缩时的能量来源,释放能量后变为肌酐,由肾脏排泄。2、肌酐增⾼⻅于肾脏损害,急、慢性肾功能不全及⼼功能不全等。3、建议到医院肾内科就诊进⼀步检查,明确诊断。");

Map<String, String> map3 = new HashMap<>();

map3.put("code", "屈光不正");

map3.put("content", "注意⽤眼卫⽣,定期眼科随访。");

List<Map<String, String>> list = List.of(map1, map2, map3);

for (int i = 0; i < list.size(); i++) {

XWPFParagraph xwpfParagraph = OfficeUtils.insertNewParagraph(document.getParagraphs(), document, index + i);

// 创建表格

XmlCursor cursor = xwpfParagraph.getCTP().newCursor();

// 在指定游标位置插入表格

XWPFTable table = document.insertNewTbl(cursor);

CTTblPr tblPr = table.getCTTbl().getTblPr();

// 设置表格整体样式

tblPr.addNewTblW().setW(BigInteger.valueOf(7920)); // 设置表格宽度

XWPFTableRow dataRow = OfficeUtils.createRow(table, 0);

// 创建第一列

XWPFTableCell cell1 = OfficeUtils.createCell(dataRow, 0);

// 在段落中创建一个新的文本运行

XWPFRun run1 = cell1.getParagraphs().get(0).createRun();

// 设置字体为宋体

run1.setFontFamily("宋体");

// 设置字号为四号(14磅)

run1.setFontSize(14);

// 添加文本内容

run1.setText(list.get(i).get("code"));

OfficeUtils.setTheLandscapeHeader(cell1, 0.2);

// 使用十六进制颜色码,这里是灰色

cell1.setColor("C0C0C0");

// 创建第二列

XWPFTableCell cell2 = OfficeUtils.createCell(dataRow, 1);

// 清空单元格内容(可选,如果需要)

cell2.removeParagraph(0);

// 截断内容为多个段落

Matcher matcher = pattern.matcher(list.get(i).get("content").trim());

List<String> matches = new ArrayList<>();

while (matcher.find()) {

matches.add(matcher.group());

}

if (matches.size() == 0) {

matches.add(list.get(i).get("content").trim());

}

for (int j = 0; j < matches.size(); j++) {

XWPFParagraph para = cell2.addParagraph();

if (j != 0){

para = cell2.addParagraph();

}

// 添加文本内容

para.createRun().setText(matches.get(j));

para.setAlignment(ParagraphAlignment.LEFT); // 设置对齐方式

}

OfficeUtils.setsTheCellWidthLeft(cell2, 0.8);

}

}

在这里插入图片描述

4、自定义图表

4.1 思路

JFreeChart

JFreeChart是一个开源的Java图表库,专为JAVA平台设计,用于生成高质量的2D图表。

4.1.1 概述

JFreeChart是一个完全使用JAVA语言编写的图表绘制类库。它最初由David Gilbert创建,自2001年以来一直在持续开发和更新,目前已成为Java社区中广泛使用的图表库之一。JFreeChart是一个开源项目,遵循GNU通用公共许可证(LGPL),允许在专有应用程序中使用。

4.1.2 支持的图表类型

JFreeChart支持多种图表类型,包括但不限于:

饼图(Pie charts)柱状图(Bar charts)散点图(Scatter plots)时序图(Time series)甘特图(Gantt charts)线形图(Line charts)气泡图(Bubble charts)热力图(Heatmaps)

4.1.3 特性

定制能力:提供大量的定制选项,包括颜色、字体、标签、图例、网格线、数据点等,以满足各种设计需求。数据源:接受各种数据结构作为输入,如数组、列表或CategoryDataset和TimeSeriesDataset对象。输出类型:支持多种输出类型,包括Swing组件、图像文件(PNG、JPEG)、矢量图形文件格式(PDF、EPS、SVG)等。交互性:具有一定的交互功能,如缩放、平移等。

通过 JFreeChart 创建图表,将图表转换为图像格式(如PNG或JPEG),然后将图像解析成InputStream 写入到Word文档的相应位置中。

4.2 准备模板

在这里插入图片描述

4.3 导入依赖

<code> <dependency>

<groupId>org.jfree</groupId>

<artifactId>jfreechart</artifactId>

<version>1.5.3</version>

</dependency>

4.4 图表生成工具类 ChartWithChineseExample

在使用org.jfree.chart库生成图表时,如果遇到中文无法正常显示的问题,通常是字体设置的问题。JFreeChart默认使用的字体可能不支持中文字符。要解决这个问题,你需要指定一个支持中文的字体。以下是解决此问题的一般步骤:

步骤 1: 准备字体文件

首先,你需要一个支持中文的TrueType字体文件(.ttf),如宋体(SimSun.ttf)、微软雅黑(msyh.ttf)等。这些字体文件通常可以在Windows系统的C:\Windows\Fonts目录下找到,或者你可以从互联网上下载。

字体文件包可以从这里下载:office字体文件包

步骤 2: 注册字体到FontFactory

在你的Java程序中,使用FontFactory.register()方法注册你的中文字体文件。例如,如果你有SimSun.ttf这个字体文件,可以这样做:

/**

* 注册中文字体

*/

public static void registerChineseFont() {

// 注册中文字体(这里假设已经将字体文件放置在项目的resources目录下)

InputStream fontStream = ChartWithChineseExample.class.getResourceAsStream("/font/SIMSUN.TTC"); // 路径根据实际情况调整

try {

Font customFont = Font.createFont(Font.TRUETYPE_FONT, fontStream).deriveFont(Font.PLAIN, 12);

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();

ge.registerFont(customFont);

} catch (FontFormatException | IOException e) {

e.printStackTrace();

}

}

步骤 3: 设置图表具体位置的字体
柱状图:

// 示例字体为宋体,常规,14号

Font axisLabelFont = new Font("SimSun", Font.PLAIN, 14);

// X轴

chart.getCategoryPlot().getDomainAxis().setLabelFont(axisLabelFont);

chart.getCategoryPlot().getDomainAxis().setTickLabelFont(axisLabelFont);

// Y轴

chart.getCategoryPlot().getRangeAxis().setLabelFont(axisLabelFont);

chart.getCategoryPlot().getRangeAxis().setTickLabelFont(axisLabelFont);

饼图:

chart.getTitle().setFont(new Font("SimSun", Font.BOLD, 18));

chart.getLegend().setItemFont(new Font("SimSun", Font.PLAIN, 12));

// 获取饼图的plot对象,以便进行进一步定制

PiePlot3D plot = (PiePlot3D) chart.getPlot();

// 设置标签字体

plot.setLabelFont(new Font("SimSun", Font.PLAIN, 14));

// 设置无数据信息字体(如果需要)

plot.setNoDataMessageFont(new Font("SimSun", Font.PLAIN, 18));

折线图:

// 设置字体

chart.getTitle().setFont(new Font("SimSun", Font.BOLD, 18));

chart.getLegend().setItemFont(new Font("SimSun", Font.PLAIN, 12));

CategoryPlot plot = (CategoryPlot) chart.getPlot();

plot.getDomainAxis().setLabelFont(new Font("SimSun", Font.PLAIN, 12));

plot.getRangeAxis().setLabelFont(new Font("SimSun", Font.PLAIN, 12));

plot.getDomainAxis().setTickLabelFont(new Font("SimSun", Font.PLAIN, 12));

plot.getRangeAxis().setTickLabelFont(new Font("SimSun", Font.PLAIN, 12));

完整代码:

package com.example.demo.uitls;

import lombok.extern.slf4j.Slf4j;

import org.jfree.chart.ChartFactory;

import org.jfree.chart.JFreeChart;

import org.jfree.chart.plot.CategoryPlot;

import org.jfree.chart.plot.PiePlot3D;

import org.jfree.chart.plot.PlotOrientation;

import org.jfree.data.category.DefaultCategoryDataset;

import org.jfree.data.general.DefaultPieDataset;

import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;

import java.awt.*;

import java.awt.image.BufferedImage;

import java.io.*;

/**

* ChartWithChineseExample : 图表生成工具类

*

* @author zyw

* @create 2024-06-25 16:20

*/

@Slf4j

@Component

public class ChartWithChineseExample {

// 柱状图临时文件名

public final static String BAR_CHART_FILE_NAME = "BAR_CHART.png";

// 饼图临时文件名

public final static String PIE_CHART_FILE_NAME = "PIE_CHART.png";

// 折线图临时文件名

public final static String LINE_CHART_FILE_NAME = "LINE_CHART.png";

public static InputStream lineChartGeneration(String title, String x, String y, DefaultCategoryDataset dataset) {

registerChineseFont();

JFreeChart chart = ChartFactory.createLineChart(

title, // 图表标题

x, // X轴标签

y, // Y轴标签

dataset, // 数据集

PlotOrientation.VERTICAL, // 图表方向

true, // 是否显示图例

true, // 是否生成工具提示

false // 是否生成URL链接

);

chart.getTitle().setFont(new Font("SimSun", Font.BOLD, 18));

chart.getLegend().setItemFont(new Font("SimSun", Font.PLAIN, 14));

// 示例字体为宋体,常规,14号

Font axisLabelFont = new Font("SimSun", Font.PLAIN, 14);

// X轴

chart.getCategoryPlot().getDomainAxis().setLabelFont(axisLabelFont);

chart.getCategoryPlot().getDomainAxis().setTickLabelFont(axisLabelFont);

// Y轴

chart.getCategoryPlot().getRangeAxis().setLabelFont(axisLabelFont);

chart.getCategoryPlot().getRangeAxis().setTickLabelFont(axisLabelFont);

try {

// 将图表转换为字节数组

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

BufferedImage chartImage = chart.createBufferedImage(400, 300); // 设置图像的宽高

ImageIO.write(chartImage, "png", outputStream);

byte[] chartBytes = outputStream.toByteArray();

// 将字节数组转换为InputStream

InputStream inputStream = new ByteArrayInputStream(chartBytes);

return inputStream;

} catch (IOException e) {

log.error("折线图图生成异常");

return null;

}

}

/**

* 饼图生成

*

* @param title 标题

* @param dataset 数据集

* @return

*/

public static InputStream pieChartGeneration(String title, DefaultPieDataset dataset) {

registerChineseFont();

// 使用数据集创建饼图

JFreeChart chart = ChartFactory.createPieChart3D(

title, // 图表标题

dataset, // 数据集

true, // 是否显示图例

true, // 是否生成工具提示

false // 是否生成URL链接

);

chart.getTitle().setFont(new Font("SimSun", Font.BOLD, 18));

chart.getLegend().setItemFont(new Font("SimSun", Font.PLAIN, 12));

// 获取饼图的plot对象,以便进行进一步定制

PiePlot3D plot = (PiePlot3D) chart.getPlot();

// 设置标签字体

plot.setLabelFont(new Font("SimSun", Font.PLAIN, 14));

// 设置无数据信息字体(如果需要)

plot.setNoDataMessageFont(new Font("SimSun", Font.PLAIN, 18));

try {

// 将图表转换为字节数组

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

BufferedImage chartImage = chart.createBufferedImage(400, 300); // 设置图像的宽高

ImageIO.write(chartImage, "png", outputStream);

byte[] chartBytes = outputStream.toByteArray();

// 将字节数组转换为InputStream

InputStream inputStream = new ByteArrayInputStream(chartBytes);

return inputStream;

} catch (IOException e) {

log.error("饼图生成异常");

return null;

}

}

/**

* 注册中文字体

*/

public static void registerChineseFont() {

// 注册中文字体(这里假设已经将字体文件放置在项目的resources目录下)

InputStream fontStream = ChartWithChineseExample.class.getResourceAsStream("/font/SIMSUN.TTC"); // 路径根据实际情况调整

try {

Font customFont = Font.createFont(Font.TRUETYPE_FONT, fontStream).deriveFont(Font.PLAIN, 12);

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();

ge.registerFont(customFont);

} catch (FontFormatException | IOException e) {

e.printStackTrace();

}

}

/**

* 创建柱状图表

*

* @param dataset 数据集

* @return

*/

public static InputStream createChartPanel(String title, String x, String y, DefaultCategoryDataset dataset) {

registerChineseFont();

// 创建图表

JFreeChart chart = ChartFactory.createBarChart(

title, // 图表标题

x, // X轴标签

y, // Y轴标签

dataset,

PlotOrientation.VERTICAL,

true, // 是否显示图例

true, // 是否使用工具提示

false // 是否生成URL链接

);

// 设置字体

chart.getTitle().setFont(new Font("SimSun", Font.BOLD, 18));

chart.getLegend().setItemFont(new Font("SimSun", Font.PLAIN, 12));

CategoryPlot plot = (CategoryPlot) chart.getPlot();

plot.getDomainAxis().setLabelFont(new Font("SimSun", Font.PLAIN, 12));

plot.getRangeAxis().setLabelFont(new Font("SimSun", Font.PLAIN, 12));

plot.getDomainAxis().setTickLabelFont(new Font("SimSun", Font.PLAIN, 12));

plot.getRangeAxis().setTickLabelFont(new Font("SimSun", Font.PLAIN, 12));

try {

// 将图表转换为字节数组

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

BufferedImage chartImage = chart.createBufferedImage(400, 300); // 设置图像的宽高

ImageIO.write(chartImage, "png", outputStream);

byte[] chartBytes = outputStream.toByteArray();

// 将字节数组转换为InputStream

InputStream inputStream = new ByteArrayInputStream(chartBytes);

return inputStream;

} catch (IOException e) {

log.error("柱状图生成异常");

return null;

}

}

}

4.5 业务层 OfficeServicel

在word中遍历所有段落,找到需要插入图表的段落索引。

此处省略上诉已展示代码。

/**

* OfficeServiceImpl :

*

* @author zyw

* @create 2024-06-24 15:41

*/

@Service

@Slf4j

public class OfficeServiceImpl implements OfficeService {

private static final String HEADER_2_1 = "营养成分摄入比例";

private static final String HEADER_2_2 = "心率血氧检查";

private static final String HEADER_2_3 = "睡眠质量趋势";

@Override

public XWPFDocument getHealthReport(HealthReportQuery query) {

try {

FileInputStream fileInputStream = SpringUtils.convertInputStreamToFileInputStream(resourceLoader.getResource(PERSONAL_HEALTH_REPORT_TEMPLATE).getInputStream());

XWPFDocument xwpfDocument = new XWPFDocument(fileInputStream);

// 替换文本数据构建

OfficeUtils.paragraphTextFilling(xwpfDocument, OfficeUtils.objectToMap(query));

// 在基本信息表格中填充数据

fillInTable(xwpfDocument, query);

// 插入体育运动水平表格

int index3 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_1_3);

handleTableOne(xwpfDocument, index3, query.getSportsLevel());

// 插入体重指数表格

int index4 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_1_4);

handleTableTwo(xwpfDocument, index4, query.getHeight(), query.getWeight());

// 插入历史体重

int index5 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_2_1);

insertChartOne(xwpfDocument, index5);

// 插入心率检查

int index6 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_2_2);

insertChartTwo(xwpfDocument, index6);

// 插入睡眠质量趋势

int index7 = OfficeUtils.findParagraphIndexByText(xwpfDocument, HEADER_2_3);

insertChartThree(xwpfDocument, index7);

return xwpfDocument;

} catch (Exception e) {

log.info("获取健康报告失败", e);

return null;

}

}

/**

* 获取文本在文档中的索引

*

* @param doc 文档

* @param text 文本标识

* @return

*/

public static int findParagraphIndexByText(XWPFDocument doc, String text) {

// 获取所有段落

List<XWPFParagraph> paragraphs = doc.getParagraphs();

// 查找目标段落

int targetParagraphIndex = -1;

for (int i = 0; i < paragraphs.size(); i++) {

if (paragraphs.get(i).getText().contains(text)) {

targetParagraphIndex = i;

break;

}

}

return targetParagraphIndex;

}

/**

* 插入图表 1

*

* @param document

* @param index

* @throws Exception

*/

public void insertChartOne(XWPFDocument document, Integer index) throws Exception {

// 填充图表数据

DefaultPieDataset<String> dataset = new DefaultPieDataset<String>();

dataset.setValue("碳水化合物(30%)", 30);

dataset.setValue("蛋白质(30%)", 30);

dataset.setValue("脂肪(25%)", 25);

dataset.setValue("纤维等营养素(15%)", 15);

// 创建图表示例

InputStream chartPanel = ChartWithChineseExample.pieChartGeneration("营养成分摄入比例", dataset);

// 获取所有段落

List<XWPFParagraph> paragraphs = document.getParagraphs();

// 在目标段落后添加一个新的段落

XWPFParagraph paragraph = document.insertNewParagraph(paragraphs.get(index + 1).getCTP().newCursor().newCursor());

// 设置段落的样式和属性,实现换行

paragraph.setWordWrap(true); // 设置自动换行

// 设置段落水平居中

paragraph.setAlignment(ParagraphAlignment.CENTER);

// 设置段落内文字(这里是空格)垂直居中

paragraph.setVerticalAlignment(TextAlignment.CENTER);

// 调整行距以确保图片上下居中,这一步可能需要根据实际情况调整

XWPFRun run = paragraph.createRun();

run.addPicture(chartPanel, XWPFDocument.PICTURE_TYPE_PNG, ChartWithChineseExample.BAR_CHART_FILE_NAME, Units.toEMU(400), Units.toEMU(300));

}

/**

* 插入图表2 心率血氧

*

* @param document

* @param index

*/

public void insertChartTwo(XWPFDocument document, Integer index) throws Exception {

// 填充图表数据

DefaultCategoryDataset dataset = new DefaultCategoryDataset();

dataset.addValue(77, "心率", "2024-06-23");

dataset.addValue(85, "心率", "2024-06-24");

dataset.addValue(99, "心率", "2024-06-25");

dataset.addValue(92.76, "血氧饱和度", "2024-06-23");

dataset.addValue(98.74, "血氧饱和度", "2024-06-24");

dataset.addValue(94.2, "血氧饱和度", "2024-06-25");

// 创建图表示例

InputStream chartPanel = ChartWithChineseExample.createChartPanel("心率和血氧饱和度图表", "日期", "心率(次/分)、血氧饱和度(%)", dataset);

// 获取所有段落

List<XWPFParagraph> paragraphs = document.getParagraphs();

// 在目标段落后添加一个新的段落

XWPFParagraph paragraph = document.insertNewParagraph(paragraphs.get(index + 1).getCTP().newCursor().newCursor());

// 设置段落的样式和属性,实现换行

paragraph.setWordWrap(true); // 设置自动换行

// 设置段落水平居中

paragraph.setAlignment(ParagraphAlignment.CENTER);

// 设置段落内文字(这里是空格)垂直居中

paragraph.setVerticalAlignment(TextAlignment.CENTER);

// 调整行距以确保图片上下居中,这一步可能需要根据实际情况调整

XWPFRun run = paragraph.createRun();

run.addPicture(chartPanel, XWPFDocument.PICTURE_TYPE_PNG, ChartWithChineseExample.PIE_CHART_FILE_NAME, Units.toEMU(400), Units.toEMU(300));

}

/**

* 插入图表3 睡眠质量趋势

*

* @param document

* @param index

* @throws Exception

*/

public void insertChartThree(XWPFDocument document, Integer index) throws Exception {

// 填充图表数据

DefaultCategoryDataset dataset = new DefaultCategoryDataset();

dataset.addValue(7.8, "起床时间", "06/18");

dataset.addValue(8, "起床时间", "06/19");

dataset.addValue(7.5, "起床时间", "06/20");

dataset.addValue(8.3, "起床时间", "06/21");

dataset.addValue(9, "起床时间", "06/22");

dataset.addValue(9.5, "起床时间", "06/23");

dataset.addValue(23, "睡眠时间", "06/18");

dataset.addValue(24, "睡眠时间", "06/19");

dataset.addValue(22.6, "睡眠时间", "06/20");

dataset.addValue(23.2, "睡眠时间", "06/21");

dataset.addValue(21.8, "睡眠时间", "06/22");

dataset.addValue(23.7, "睡眠时间", "06/23");

// 创建图表示例

InputStream chartPanel = ChartWithChineseExample.lineChartGeneration("睡眠质量趋势", "日期", "睡眠时间", dataset);

// 获取所有段落

List<XWPFParagraph> paragraphs = document.getParagraphs();

// 在目标段落后添加一个新的段落

XWPFParagraph paragraph = OfficeUtils.insertNewParagraph(paragraphs, document,index);

// 设置段落的样式和属性,实现换行

paragraph.setWordWrap(true); // 设置自动换行

// 设置段落水平居中

paragraph.setAlignment(ParagraphAlignment.CENTER);

// 设置段落内文字(这里是空格)垂直居中

paragraph.setVerticalAlignment(TextAlignment.CENTER);

// 调整行距以确保图片上下居中,这一步可能需要根据实际情况调整

XWPFRun run = paragraph.createRun();

run.addPicture(chartPanel, XWPFDocument.PICTURE_TYPE_PNG, ChartWithChineseExample.LINE_CHART_FILE_NAME, Units.toEMU(400), Units.toEMU(300));

}

}

4.6 导出效果

Word:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。