JFreeChart 生成Word图表

news2024/12/23 9:23:05

文章目录

  • 1 思路
    • 1.1 概述
    • 1.2 支持的图表类型
    • 1.3 特性
  • 2 准备模板
  • 3 导入依赖
  • 4 图表生成工具类 ChartWithChineseExample
    • 步骤 1: 准备字体文件
    • 步骤 2: 注册字体到`FontFactory`
    • 步骤 3: 设置图表具体位置的字体
      • 柱状图:
      • 饼图:
      • 折线图:
      • 完整代码:
  • 5 业务层 OfficeServicel
  • 6 通用工具类 OfficeUtils
  • 7 控制层 OfficeController
  • 8 导出效果

1 思路

JFreeChart

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

1.1 概述

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

1.2 支持的图表类型

  • JFreeChart支持多种图表类型,包括但不限于:
    • 饼图(Pie charts)
    • 柱状图(Bar charts)
    • 散点图(Scatter plots)
    • 时序图(Time series)
    • 甘特图(Gantt charts)
    • 线形图(Line charts)
    • 气泡图(Bubble charts)
    • 热力图(Heatmaps)

1.3 特性

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

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

2 准备模板

在这里插入图片描述

3 导入依赖

        <dependency>
            <groupId>org.jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.5.3</version>
        </dependency>

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;
        }
    }

}

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);
            // 插入历史体重
            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));
    }
}

6 通用工具类 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 paragraphs 段落集合
     * @param document   文档
     * @param index      插入位置
     * @return 新段落
     */
    public static XWPFParagraph insertNewParagraph(List<XWPFParagraph> paragraphs, XWPFDocument document, Integer index) {
        if (paragraphs.size() == index + 1) {
            return document.createParagraph();
        } else {
            return document.insertNewParagraph(paragraphs.get(index + 1).getCTP().newCursor().newCursor());
        }
    }

    /**
     * 设置表格宽度去除边框
     *
     * @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 setsTheCellWidthLeft(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.LEFT);
        }
    }

    /**
     * 设置表格单元格宽度
     *
     * @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;
    }

    /**
     * 对象转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;
        }
    }
}

7 控制层 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);
    }

}

在这里插入图片描述

8 导出效果

Word效果:
在这里插入图片描述

PDF效果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1865296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

韩顺平0基础学java——第30天

p600-611 坦克大战&#xff01; 艰难推进中 坦克大战-子弹 发射子弹 1.当发射一颗子弹后&#xff0c;就相当于启动一个线程 2.玩家拥有子弹对象&#xff0c;当按下J时&#xff0c;就启动发射行为&#xff08;线程&#xff09;&#xff0c;让子弹不停移动&#xff0c;形成…

Ubuntu20.04安装python2和python3及版本配置

Ubuntu20.04安装python2和python3及版本配置_ubuntu 20.04 python3-CSDN博客https://blog.csdn.net/pangc2014/article/details/117407413 >>>ubuntu 安装源码python2_mob649e8161c39d的技术博客_51CTO博客https://blog.51cto.com/u_16175489/7327966

让python的报错代码只显示第一层

在 Python 中&#xff0c;sys.tracebacklimit 是 sys 模块中的一个属性&#xff0c;它用于控制在错误发生时&#xff0c;Python 解释器显示的堆栈追踪&#xff08;traceback&#xff09;的深度。 具体来说&#xff1a; • 默认行为&#xff1a;当出现未处理的异常时&#xff…

华为OD机试 - 掌握单词个数(Java 2024 D卷 100分)

华为OD机试 2024D卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;D卷C卷A卷B卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测…

Spark运行spark-shell与hive运行时均报错的一种解决方案

环境按照尚硅谷的配置的。 在运行hive的时候&#xff0c;报错代码为30041&#xff0c;无法执行insert语句。 在运行spark-shell的时候&#xff0c;报错&#xff0c;无法进入到shell脚本中。 可能的问题&#xff1a; 对集群设置的域名与集群的主机名称不一致。 例如&#xff1a;…

C#之Delta并联机械手的视觉相机标定与形状匹配

本文导读 上节课程我们讲述了如何建立Delta并联机械手正逆解&#xff0c;本节课程我们主要讲解如何通过C#语言开发正运动Delta并联机械手视觉流水线同步分拣的视觉部分。 VPLC711硬件介绍 VPLC711是正运动推出的一款基于x86平台和Windows操作系统的高性能机器视觉EtherCAT运…

75. UE5 RPG 创建场景摆放部件蓝图

这一篇文章来点简单的内容&#xff0c;相当于我们使用蓝图创建类似于unity的预制体。 创建一个一个柱子蓝图 首先&#xff0c;我们创建一个立柱的蓝图&#xff0c;将我们之前创建的柱子上面含有火焰和灯光的部分合并成一个蓝图&#xff0c;方便往场景内添加。 点击创建一个基…

Docker系列之安全

Docker的安全前言一、Docker 容器与虚拟机的区别 1. 隔离与共享 2. 性能与损耗二、Docker 存在的安全问题 1.Docker 自身漏洞 2.Docker 源码问题三、 Docker 架构缺陷与安全机制 1. 容器之间的局域网攻击 2. DDoS 攻击耗尽资源 3. 有漏…

ACL 2023事件相关(事件抽取、事件关系抽取、事件预测等)论文汇总

ACL 2023事件抽取相关(事件抽取、事件关系抽取、事件预测等)论文汇总&#xff0c;后续会更新全部的论文讲解。 Event Extraction Code4Struct: Code Generation for Few-Shot Event Structure Prediction 数据集&#xff1a;ACE 2005 动机&#xff1a;与自然语言相比&#xf…

利用maven命令往本地仓库添加jar包

一&#xff1a;遇到问题 有些jar包在中央仓库没有&#xff0c;需要手动往本地仓库添加&#xff0c;方便以后打包使用。 比如&#xff1a;添加红框这个依赖&#xff0c;现在爆红 二&#xff1a;解决办法 **第一步&#xff1a;**打开idea&#xff0c;找到运行按钮旁边的框&am…

Android集成高德地图SDK(2)

1.解压下载的压缩包&#xff0c;找到AMap_Android_SDK_All\AMap3DMap_DemoDocs\AMap_Android_API_3DMap_Demo\AMap3DDemo\app\libs&#xff0c;复制libs里的所有文件&#xff0c;将其粘贴到Android工程的libs目录下&#xff0c;如图所示。 2.打开app下的build.gradle&#xff0…

无忧易售升级:一键设置图片分辨率,赋能十大跨境电商平台

在电商领域&#xff0c;产品图片的品质直接影响着顾客的购买决策与品牌形象的塑造。无忧易售ERP特推出图片分辨率修改功能&#xff0c;为电商卖家们提供更专业的图像优化工具&#xff0c;让每一像素都成为吸引客户的秘密武器&#xff01; 一、Allegro、OZON、Coupang、Cdiscou…

数据分析python基础实战分析

数据分析python基础实战分析 安装python&#xff0c;建议安装Anaconda 【Anaconda下载链接】https://repo.anaconda.com/archive/ 记得勾选上这个框框 安装完后&#xff0c;然后把这两个框框给取消掉再点完成 在电脑搜索框输入"Jupyter"&#xff0c;牛马启动&am…

低代码:释放企业创新力的钥匙

近年来&#xff0c;随着信息技术的不断发展&#xff0c;企业对于快速开发应用程序的需求越来越迫切。然而&#xff0c;传统的软件开发过程常常耗时费力&#xff0c;限制了企业的创新潜力。于是&#xff0c;低代码应运而生&#xff0c;成为解决开发难题的一把利器。 低代码开发…

完整代码Python爬取豆瓣电影详情数据

完整代码Python爬取豆瓣电影详情数据 引言 在数据科学和网络爬虫的世界里&#xff0c;豆瓣电影是一个丰富的数据源。在本文中&#xff0c;我们将探讨如何使用Python语言&#xff0c;结合requests和pyquery库来爬取豆瓣电影的详情页面数据。我们将通过一个具体的电影详情页面作…

节流工具,避免操作太频繁

ThrottleUtil 用于保证某个操作在一定时间内只执行一次的工具。 package com.cashpro.kash.lending.loan.utils;/*** <pre>* Created by zhuguohui* Date: 2024/6/26* Time: 13:43* Desc:用于节流执行任务,限制任务执行的频次* </pre>*/import android.os.Handle…

给前端小白的11个建议(少走弯路)

作为一个编程4年的的前端工程师&#xff0c;一路走来踩过许多坑。希望我的经验能让你少踩些坑&#xff0c;在编程的路上走的更顺些&#xff01; 1. 禁用var声明 只使用const或let声明变量。并且首选const&#xff0c;当一个变量需要重新赋值时&#xff0c;才使用let。并且在创…

旧衣回收小程序开发:回收市场的新机遇

当下&#xff0c;旧衣服回收已经成为了一种流行趋势&#xff0c;居民都将闲置的衣物进行回收&#xff0c;旧衣回收市场规模在不断增加。随着市场规模的扩大&#xff0c;为了让居民更加便利地进行回收&#xff0c;线上回收小程序也应运而生&#xff0c;为大众打造了一个线上回收…

windows安装Nacos并使用

Nacos&#xff08;前身为阿里巴巴的Nacos Config和Nacos Discovery&#xff09;是一个开源的动态服务发现、配置和服务管理平台&#xff0c;由阿里巴巴开发并维护。它提供了一种简单且易于使用的方式来管理微服务架构中的服务注册、发现和配置管理。 主要功能包括&#xff1a;…

[leetcode]move-zeroes 移动零

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void moveZeroes(vector<int>& nums) {int n nums.size(), left 0, right 0;while (right < n) {if (nums[right]) {swap(nums[left], nums[right]);left;}right;}} };