java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Apache POI导出Word和PPT

SpringBoot使用Apache POI实现导出Word和PPT的完整代码

作者:身如柳絮随风扬

POI-TL(POI Template Language)是一个基于Apache POI的Word模板引擎,这篇文章主要介绍了SpringBoot如何使用Apache POI实现导出Word和PPT,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

一、为什么使用POI-TL

POI-TL(POI Template Language)是一个基于Apache POI的Word模板引擎,它通过自定义标签语法,让你能以最少的代码实现复杂文档的生成。对比直接使用POI,其优势在于:

PPT方面,POI-TL不支持,我们仍用Apache POI原生API + 模板方式实现。

二、为什么使用Service接口

面向接口编程是Spring框架的核心实践,原因包括:

三、环境搭建与配置

1. 项目依赖(pom.xml关键部分)

<properties>
    <java.version>17</java.version>
    <spring-boot.version>3.5.11</spring-boot.version>
    <mybatis-plus.version>3.5.9</mybatis-plus.version>
    <poi-tl.version>1.12.2</poi-tl.version> <!-- 最新稳定版 -->
</properties>

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- POI-TL (Word模板引擎) -->
    <dependency>
        <groupId>com.deepoove</groupId>
        <artifactId>poi-tl</artifactId>
        <version>${poi-tl.version}</version>
    </dependency>

    <!-- Apache POI (用于PPT导出) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.3.0</version>
    </dependency>

    <!-- 工具类 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. 配置文件 application.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/car_report_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: yourpassword
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发时打印SQL
  global-config:
    db-config:
      id-type: auto

四、数据库设计

我们设计一张简单的汽车信息表,用于存储报告所需数据。

CREATE DATABASE IF NOT EXISTS car_report_db DEFAULT CHARACTER SET utf8mb4;

USE car_report_db;

CREATE TABLE `car` (
    `id` BIGINT AUTO_INCREMENT COMMENT '主键ID',
    `brand` VARCHAR(50) NOT NULL COMMENT '品牌',
    `model` VARCHAR(50) NOT NULL COMMENT '车型',
    `price` DECIMAL(10,2) COMMENT '指导价(万元)',
    `engine` VARCHAR(100) COMMENT '发动机',
    `max_power` INT COMMENT '最大功率(kW)',
    `max_torque` INT COMMENT '最大扭矩(N·m)',
    `acceleration` DECIMAL(3,1) COMMENT '百公里加速(s)',
    `fuel_consumption` DECIMAL(4,1) COMMENT '综合油耗(L/100km)',
    `image_url` VARCHAR(255) COMMENT '图片URL(用于PPT插入图片)',
    `description` TEXT COMMENT '车型描述',
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='汽车信息表';

-- 插入测试数据
INSERT INTO `car` (`brand`, `model`, `price`, `engine`, `max_power`, `max_torque`, `acceleration`, `fuel_consumption`, `image_url`, `description`) VALUES
('宝马', 'X5 xDrive40i', 75.99, '3.0T L6', 250, 450, 5.5, 9.1, 'https://cdn.simpleicons.org/bmw', '豪华中大型SUV,操控与舒适兼备。'),
('特斯拉', 'Model Y 长续航版', 34.99, '纯电动', 331, 559, 5.0, 0.0, 'https://cdn.simpleicons.org/tesla', '纯电动SUV,续航持久,智能科技。'),
('奔驰', 'A6L 45 TFSI', 45.89, '2.0T L4', 180, 370, 7.5, 7.5, 'https://cdn.simpleicons.org/mercedes', '商务轿车典范,空间宽敞,科技感强。');

五、实体类与Mapper

实体类 Car.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@Data
@TableName("car")
public class Car {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String brand;
    private String model;
    private BigDecimal price;
    private String engine;
    private Integer maxPower;
    private Integer maxTorque;
    private BigDecimal acceleration;
    private BigDecimal fuelConsumption;
    private String imageUrl;
    private String description;
}

Mapper CarMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CarMapper extends BaseMapper<Car> {
}

六、Service接口与实现类

1. 导出服务接口 ReportService.java

import jakarta.servlet.http.HttpServletResponse;

public interface ReportService {
    /**
     * 导出Word报告
     * @param carId 汽车ID
     * @param response HttpServletResponse用于输出文件
     */
    void exportWord(Long carId, HttpServletResponse response);

    /**
     * 导出PPT报告
     * @param carId 汽车ID
     * @param response HttpServletResponse用于输出文件
     */
    void exportPpt(Long carId, HttpServletResponse response);
}

2. 实现类 ReportServiceImpl.java

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.PictureType;
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.data.Texts;
import com.deepoove.poi.data.style.Style;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.xslf.usermodel.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class ReportServiceImpl implements ReportService {

    private final CarMapper carMapper;

    @Value("${file.template-path:templates/}") // 模板存放路径
    private String templatePath;

    @Override
    @Transactional(readOnly = true) // 只读事务,提高数据库性能
    public void exportWord(Long carId, HttpServletResponse response) {
        // 1. 查询数据
        Car car = carMapper.selectById(carId);
        if (car == null) {
            throw new RuntimeException("汽车不存在");
        }

        // 2. 构建数据模型(POI-TL要求的数据结构)
        Map<String, Object> data = new HashMap<>();
        data.put("brand", car.getBrand());
        data.put("model", car.getModel());
        data.put("price", car.getPrice() + "万元");
        data.put("engine", car.getEngine());
        data.put("maxPower", car.getMaxPower() + "kW");
        data.put("maxTorque", car.getMaxTorque() + "N·m");
        data.put("acceleration", car.getAcceleration() + "秒");
        data.put("fuelConsumption", car.getFuelConsumption() + "L/100km");
        data.put("description", Texts.of(car.getDescription()).create());

        // 如果有图片,处理图片占位符 {{@image}} (假设图片存在本地或网络)
        if (car.getImageUrl() != null && !car.getImageUrl().isEmpty()) {
            try {
                // 这里简单地从类路径读取图片(实际生产应从文件服务器或URL获取)
                InputStream imageStream = new ClassPathResource("static" + car.getImageUrl()).getInputStream();
                data.put("image", Pictures.ofStream(imageStream, PictureType.PNG)
                        .size(200, 150).create()); // 设置图片宽高
            } catch (IOException e) {
                log.warn("图片读取失败: {}", car.getImageUrl(), e);
                data.put("image", null);
            }
        }

        // 3. 加载模板并渲染
        try (InputStream templateStream = new ClassPathResource(templatePath + "car_report_template.docx").getInputStream();
             XWPFTemplate template = XWPFTemplate.compile(templateStream).render(data);
             OutputStream out = response.getOutputStream()) {

            // 4. 设置响应头
            setResponseHeader(response, "car_report_" + carId + ".docx");

            // 5. 写入输出流
            template.write(out);
            out.flush();
        } catch (IOException e) {
            log.error("导出Word失败,carId: {}", carId, e);
            throw new RuntimeException("导出Word失败", e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public void exportPpt(Long carId, HttpServletResponse response) {
        // 1. 查询数据
        Car car = carMapper.selectById(carId);
        if (car == null) {
            throw new RuntimeException("汽车不存在");
        }

        // 2. 加载PPT模板 (使用Apache POI)
        try (InputStream templateStream = new ClassPathResource(templatePath + "car_report_template.pptx").getInputStream();
             XMLSlideShow ppt = new XMLSlideShow(templateStream);
             OutputStream out = response.getOutputStream()) {

            // 3. 遍历幻灯片,替换占位符
            for (XSLFSlide slide : ppt.getSlides()) {
                // 替换文本框内容
                for (XSLFShape shape : slide.getShapes()) {
                    if (shape instanceof XSLFTextShape) {
                        XSLFTextShape textShape = (XSLFTextShape) shape;
                        String text = textShape.getText();
                        if (text != null) {
                            text = text.replace("{{brand}}", car.getBrand())
                                    .replace("{{model}}", car.getModel())
                                    .replace("{{price}}", car.getPrice() + "万元")
                                    .replace("{{engine}}", car.getEngine())
                                    .replace("{{maxPower}}", car.getMaxPower() + "kW")
                                    .replace("{{maxTorque}}", car.getMaxTorque() + "N·m")
                                    .replace("{{acceleration}}", car.getAcceleration() + "秒")
                                    .replace("{{fuelConsumption}}", car.getFuelConsumption() + "L/100km")
                                    .replace("{{description}}", car.getDescription());
                            textShape.setText(text);
                        }
                    }
                    // 如果是图片占位符,插入图片 (需要预先在模板中放置一个图片,并识别它)
                    if (shape instanceof XSLFPictureShape) {
                        // 通常我们通过形状名称来标记占位图片
                        if ("imagePlaceholder".equals(shape.getShapeName())) {
                            // 移除原图片,添加新图片(略复杂,此处仅演示思路)
                            // 实际应用中建议用文本框占位,然后新建图片
                        }
                    }
                }
            }

            // 4. 设置响应头
            setResponseHeader(response, "car_report_" + carId + ".pptx");

            // 5. 写入输出流
            ppt.write(out);
            out.flush();
        } catch (IOException e) {
            log.error("导出PPT失败,carId: {}", carId, e);
            throw new RuntimeException("导出PPT失败", e);
        }
    }

    /**
     * 设置下载响应头
     */
    private void setResponseHeader(HttpServletResponse response, String filename) {
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);
    }
}

说明

七、Controller测试接口

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletResponse;

@RestController
@RequestMapping("/api/report")
@RequiredArgsConstructor
public class ReportController {

    private final ReportService reportService;

    @GetMapping("/word/{carId}")
    public void exportWord(@PathVariable Long carId, HttpServletResponse response) {
        reportService.exportWord(carId, response);
    }

    @GetMapping("/ppt/{carId}")
    public void exportPpt(@PathVariable Long carId, HttpServletResponse response) {
        reportService.exportPpt(carId, response);
    }
}

测试地址(请先配好模板)

八、模板设计指南(Word和PPT)

Word模板(car_report_template.docx)

使用POI-TL标签语法,在Word中设计一个美观的报告样式。以下示例标签:

标题区{{brand}} {{model}} 汽车详细报告

基本参数表格

项目参数
品牌{{brand}}
型号{{model}}
指导价{{price}}
发动机{{engine}}
最大功率{{maxPower}}
最大扭矩{{maxTorque}}
0-100km/h{{acceleration}}
综合油耗{{fuelConsumption}}

设计建议(模板根据自己来设计Word样式)

PPT模板(car_report_template.pptx)

使用Apache POI原生操作,我们通常预置占位符文本,如{{brand}},在代码中遍历形状并替换。图片替换需要更复杂的逻辑,可以预先插入一个占位图片(如一个灰色方块),并设置其形状名称为imagePlaceholder,然后在代码中定位并替换为实际图片。

设计建议(模板根据自己来设计PPT样式)

九、运行与测试

十、总结

本示例完整展示了使用Spring Boot + MyBatis-Plus + POI-TL + Apache POI实现企业级Word和PPT导出的流程。要点如下:

可以基于此扩展更多功能,如批量导出、异步处理、使用消息队列等,以满足更高并发要求。

以上就是SpringBoot使用Apache POI实现导出Word和PPT的完整代码的详细内容,更多关于Apache POI导出Word和PPT的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文