SpringBoot使用Apache?POI實(shí)現(xiàn)導(dǎo)出Word和PPT的完整代碼
一、為什么使用POI-TL
POI-TL(POI Template Language)是一個(gè)基于Apache POI的Word模板引擎,它通過(guò)自定義標(biāo)簽語(yǔ)法,讓你能以最少的代碼實(shí)現(xiàn)復(fù)雜文檔的生成。對(duì)比直接使用POI,其優(yōu)勢(shì)在于:
- 極簡(jiǎn)代碼:只需幾行即可完成表格、圖片、列表的渲染,無(wú)需遍歷段落和表格。
- 強(qiáng)大標(biāo)簽:支持
{{var}}文本替換,{{[#list]}}循環(huán)表格,{{@image}}插入圖片,{{+watermark}}水印等。 - 完美保留樣式:模板中的樣式(字體、顏色、布局)在渲染后完全保留,符合企業(yè)級(jí)文檔對(duì)格式的高要求。
- 社區(qū)活躍、文檔完善:是Java生成Word的主流選擇,適合生產(chǎn)環(huán)境。
PPT方面,POI-TL不支持,我們?nèi)杂肁pache POI原生API + 模板方式實(shí)現(xiàn)。
二、為什么使用Service接口
面向接口編程是Spring框架的核心實(shí)踐,原因包括:
- 解耦:Controller直接依賴(lài)接口,不關(guān)心實(shí)現(xiàn)細(xì)節(jié),便于切換實(shí)現(xiàn)(如后期改用其他模板引擎)。
- 可測(cè)試性:可輕松使用Mockito模擬接口進(jìn)行單元測(cè)試。
- 事務(wù)管理:Spring的聲明式事務(wù)
@Transactional可以標(biāo)注在接口方法上,確保數(shù)據(jù)庫(kù)操作與文件生成的一致性。 - AOP支持:便于添加日志、權(quán)限等切面。
三、環(huán)境搭建與配置
1. 項(xiàng)目依賴(lài)(pom.xml關(guān)鍵部分)
<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> <!-- 最新穩(wěn)定版 -->
</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驅(qū)動(dòng) -->
<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導(dǎo)出) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
<!-- 工具類(lèi) -->
<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 # 開(kāi)發(fā)時(shí)打印SQL
global-config:
db-config:
id-type: auto
四、數(shù)據(jù)庫(kù)設(shè)計(jì)
我們?cè)O(shè)計(jì)一張簡(jiǎn)單的汽車(chē)信息表,用于存儲(chǔ)報(bào)告所需數(shù)據(jù)。
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 '車(chē)型',
`price` DECIMAL(10,2) COMMENT '指導(dǎo)價(jià)(萬(wàn)元)',
`engine` VARCHAR(100) COMMENT '發(fā)動(dòng)機(jī)',
`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 '車(chē)型描述',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='汽車(chē)信息表';
-- 插入測(cè)試數(shù)據(jù)
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,操控與舒適兼?zhèn)洹?),
('特斯拉', 'Model Y 長(zhǎng)續(xù)航版', 34.99, '純電動(dòng)', 331, 559, 5.0, 0.0, 'https://cdn.simpleicons.org/tesla', '純電動(dòng)SUV,續(xù)航持久,智能科技。'),
('奔馳', 'A6L 45 TFSI', 45.89, '2.0T L4', 180, 370, 7.5, 7.5, 'https://cdn.simpleicons.org/mercedes', '商務(wù)轎車(chē)典范,空間寬敞,科技感強(qiáng)。');
五、實(shí)體類(lèi)與Mapper
實(shí)體類(lèi) 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接口與實(shí)現(xiàn)類(lèi)
1. 導(dǎo)出服務(wù)接口 ReportService.java
import jakarta.servlet.http.HttpServletResponse;
public interface ReportService {
/**
* 導(dǎo)出Word報(bào)告
* @param carId 汽車(chē)ID
* @param response HttpServletResponse用于輸出文件
*/
void exportWord(Long carId, HttpServletResponse response);
/**
* 導(dǎo)出PPT報(bào)告
* @param carId 汽車(chē)ID
* @param response HttpServletResponse用于輸出文件
*/
void exportPpt(Long carId, HttpServletResponse response);
}
2. 實(shí)現(xiàn)類(lèi) 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) // 只讀事務(wù),提高數(shù)據(jù)庫(kù)性能
public void exportWord(Long carId, HttpServletResponse response) {
// 1. 查詢(xún)數(shù)據(jù)
Car car = carMapper.selectById(carId);
if (car == null) {
throw new RuntimeException("汽車(chē)不存在");
}
// 2. 構(gòu)建數(shù)據(jù)模型(POI-TL要求的數(shù)據(jù)結(jié)構(gòu))
Map<String, Object> data = new HashMap<>();
data.put("brand", car.getBrand());
data.put("model", car.getModel());
data.put("price", car.getPrice() + "萬(wàn)元");
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}} (假設(shè)圖片存在本地或網(wǎng)絡(luò))
if (car.getImageUrl() != null && !car.getImageUrl().isEmpty()) {
try {
// 這里簡(jiǎn)單地從類(lèi)路徑讀取圖片(實(shí)際生產(chǎn)應(yīng)從文件服務(wù)器或URL獲?。?
InputStream imageStream = new ClassPathResource("static" + car.getImageUrl()).getInputStream();
data.put("image", Pictures.ofStream(imageStream, PictureType.PNG)
.size(200, 150).create()); // 設(shè)置圖片寬高
} 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. 設(shè)置響應(yīng)頭
setResponseHeader(response, "car_report_" + carId + ".docx");
// 5. 寫(xiě)入輸出流
template.write(out);
out.flush();
} catch (IOException e) {
log.error("導(dǎo)出Word失敗,carId: {}", carId, e);
throw new RuntimeException("導(dǎo)出Word失敗", e);
}
}
@Override
@Transactional(readOnly = true)
public void exportPpt(Long carId, HttpServletResponse response) {
// 1. 查詢(xún)數(shù)據(jù)
Car car = carMapper.selectById(carId);
if (car == null) {
throw new RuntimeException("汽車(chē)不存在");
}
// 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()) {
// 替換文本框內(nèi)容
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() + "萬(wàn)元")
.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);
}
}
// 如果是圖片占位符,插入圖片 (需要預(yù)先在模板中放置一個(gè)圖片,并識(shí)別它)
if (shape instanceof XSLFPictureShape) {
// 通常我們通過(guò)形狀名稱(chēng)來(lái)標(biāo)記占位圖片
if ("imagePlaceholder".equals(shape.getShapeName())) {
// 移除原圖片,添加新圖片(略復(fù)雜,此處僅演示思路)
// 實(shí)際應(yīng)用中建議用文本框占位,然后新建圖片
}
}
}
}
// 4. 設(shè)置響應(yīng)頭
setResponseHeader(response, "car_report_" + carId + ".pptx");
// 5. 寫(xiě)入輸出流
ppt.write(out);
out.flush();
} catch (IOException e) {
log.error("導(dǎo)出PPT失敗,carId: {}", carId, e);
throw new RuntimeException("導(dǎo)出PPT失敗", e);
}
}
/**
* 設(shè)置下載響應(yīng)頭
*/
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);
}
}
說(shuō)明:
- 使用了
@Transactional(readOnly = true)確保數(shù)據(jù)庫(kù)查詢(xún)?cè)谑聞?wù)中執(zhí)行,但不會(huì)鎖定數(shù)據(jù)。 - 通過(guò)
ClassPathResource加載模板文件(放在resources/templates/下)。 - 圖片處理部分簡(jiǎn)化,實(shí)際生產(chǎn)可能涉及從文件服務(wù)器下載、緩存等。
- 響應(yīng)頭設(shè)置支持中文文件名。
七、Controller測(cè)試接口
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);
}
}
測(cè)試地址(請(qǐng)先配好模板):
- Word導(dǎo)出:
http://localhost:8080/api/report/word/1 - PPT導(dǎo)出:
http://localhost:8080/api/report/ppt/1
八、模板設(shè)計(jì)指南(Word和PPT)
Word模板(car_report_template.docx)
使用POI-TL標(biāo)簽語(yǔ)法,在Word中設(shè)計(jì)一個(gè)美觀的報(bào)告樣式。以下示例標(biāo)簽:
標(biāo)題區(qū):{{brand}} {{model}} 汽車(chē)詳細(xì)報(bào)告
基本參數(shù)表格:
| 項(xiàng)目 | 參數(shù) |
|---|---|
| 品牌 | {{brand}} |
| 型號(hào) | {{model}} |
| 指導(dǎo)價(jià) | {{price}} |
| 發(fā)動(dòng)機(jī) | {{engine}} |
| 最大功率 | {{maxPower}} |
| 最大扭矩 | {{maxTorque}} |
| 0-100km/h | {{acceleration}} |
| 綜合油耗 | {{fuelConsumption}} |
- 描述段落:
{{description}} - 圖片區(qū)域:使用
{{@image}}占位,可設(shè)置寬高(如{{@image}}前加標(biāo)簽{{@image}},POI-TL會(huì)自動(dòng)渲染圖片)。
設(shè)計(jì)建議(模板根據(jù)自己來(lái)設(shè)計(jì)Word樣式):
- 使用表格布局保持整齊。
- 預(yù)設(shè)好字體、顏色、邊框,POI-TL會(huì)完全保留。
- 可以添加頁(yè)眉頁(yè)腳,包含公司Logo和日期(靜態(tài)內(nèi)容直接放在模板中)。

PPT模板(car_report_template.pptx)
使用Apache POI原生操作,我們通常預(yù)置占位符文本,如{{brand}},在代碼中遍歷形狀并替換。圖片替換需要更復(fù)雜的邏輯,可以預(yù)先插入一個(gè)占位圖片(如一個(gè)灰色方塊),并設(shè)置其形狀名稱(chēng)為imagePlaceholder,然后在代碼中定位并替換為實(shí)際圖片。
設(shè)計(jì)建議(模板根據(jù)自己來(lái)設(shè)計(jì)PPT樣式):
- 首頁(yè):大標(biāo)題
{{brand}} {{model}} 汽車(chē)報(bào)告。 - 第二頁(yè):參數(shù)表格,使用PPT表格,每個(gè)單元格內(nèi)放占位符如
{{price}}。 - 第三頁(yè):描述+圖片,文本框中放
{{description}},圖片占位符命名為imagePlaceholder。



九、運(yùn)行與測(cè)試
- 啟動(dòng)MySQL,執(zhí)行SQL建庫(kù)建表并插入測(cè)試數(shù)據(jù)。
- 將Word模板
car_report_template.docx和PPT模板car_report_template.pptx放入resources/templates/目錄。 - 修改
application.yml中的數(shù)據(jù)庫(kù)連接信息。 - 啟動(dòng)Spring Boot應(yīng)用。
- 訪問(wèn)測(cè)試地址(如瀏覽器或Postman):
http://localhost:8080/api/report/word/1,應(yīng)下載Word文件。 - 打開(kāi)下載的文件,檢查數(shù)據(jù)是否正確渲染。
十、總結(jié)
本示例完整展示了使用Spring Boot + MyBatis-Plus + POI-TL + Apache POI實(shí)現(xiàn)企業(yè)級(jí)Word和PPT導(dǎo)出的流程。要點(diǎn)如下:
- POI-TL大幅簡(jiǎn)化Word生成代碼,適合復(fù)雜文檔。
- 接口+實(shí)現(xiàn)類(lèi)結(jié)構(gòu)清晰,便于維護(hù)和測(cè)試。
- 只讀事務(wù)優(yōu)化數(shù)據(jù)庫(kù)查詢(xún)。
- 模板+數(shù)據(jù)模式分離樣式與業(yè)務(wù)。
- 響應(yīng)頭設(shè)置保證中文文件名正常下載。
可以基于此擴(kuò)展更多功能,如批量導(dǎo)出、異步處理、使用消息隊(duì)列等,以滿(mǎn)足更高并發(fā)要求。
以上就是SpringBoot使用Apache POI實(shí)現(xiàn)導(dǎo)出Word和PPT的完整代碼的詳細(xì)內(nèi)容,更多關(guān)于Apache POI導(dǎo)出Word和PPT的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringMVC框架使用 Apache POI實(shí)現(xiàn)導(dǎo)出Excel
- SpringBoot使用Apache?POI實(shí)現(xiàn)導(dǎo)入導(dǎo)出Excel文件
- SpringBoot集成Apache POI實(shí)現(xiàn)Excel的導(dǎo)入導(dǎo)出
- java使用apache.poi導(dǎo)出word文件的示例代碼
- Java使用Apache.POI中HSSFWorkbook導(dǎo)出到Excel的實(shí)現(xiàn)方法
- 基于apache poi根據(jù)模板導(dǎo)出excel的實(shí)現(xiàn)方法
相關(guān)文章
關(guān)于Java整合RocketMQ實(shí)現(xiàn)生產(chǎn)消費(fèi)詳解
這篇文章主要介紹了關(guān)于Java整合RocketMQ實(shí)現(xiàn)生產(chǎn)消費(fèi)詳解,RocketMQ作為一款純java、分布式、隊(duì)列模型的開(kāi)源消息中間件,支持事務(wù)消息、順序消息、批量消息、定時(shí)消息、消息回溯等,需要的朋友可以參考下2023-05-05
Java調(diào)用SQL腳本執(zhí)行常用的方法示例
這篇文章主要給大家介紹了關(guān)于Java調(diào)用SQL腳本執(zhí)行常用的方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04
Java RMI詳細(xì)介紹及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Java RMI詳細(xì)介紹及簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02
Spring Boot右鍵maven build成功但是直接運(yùn)行main方法出錯(cuò)的解決方案
這篇文章主要介紹了Spring Boot-右鍵maven build成功但是直接運(yùn)行main方法出錯(cuò)的解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Java如何獲取一個(gè)隨機(jī)數(shù) Java猜數(shù)字小游戲
這篇文章主要為大家詳細(xì)介紹了Java如何獲取一個(gè)隨機(jī)數(shù),類(lèi)似猜數(shù)字小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03
Java FTPClient連接池的實(shí)現(xiàn)
這篇文章主要介紹了Java FTPClient連接池的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06

