当前位置:首页 » 《关注互联网》 » 正文

Java操作Word文档

2 人参与  2024年09月13日 09:22  分类 : 《关注互联网》  评论

点击全文阅读


文章目录

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(处理.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

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 */@Servicepublic 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 */@Componentpublic 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");            // 获取响应的输出流            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文档结构。

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@Slf4jpublic 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 动态表格

    /**     * 个人健康报告模板     */    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 导入依赖

        <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@Componentpublic 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@Slf4jpublic 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:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/159104.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1