加入itext依赖:
<!--用于导出pdf-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
package com.ruoyi.review.controller;
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.review.dto.AppraiseExportDataDto;
import com.ruoyi.review.dto.GradeResultDto;
import org.apache.poi.ss.usermodel.Cell;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import static com.itextpdf.text.pdf.GrayColor.GRAYBLACK;
/**
* @author :xs
* @date : 2023/3/22
* @description :
*/
@RestController
@RequestMapping("/exportPdf")
public class ExportPdfController {
@Value("${spring.watermark}")
private String springWatermark;
// 需要把“查看评分”的数据传入 GradeResultDtoList
@GetMapping("/generate-pdf")
public @ResponseBody void generatePdf(HttpServletResponse response,@RequestBody List<GradeResultDto> GradeResultDtoList) throws Exception {
// 设置响应头 可以通过在响应体中写入PDF文件的数据,然后将该响应的Content-Disposition标头设置为attachment来将其作为附件下载到客户端
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"export.pdf\"");
// 获取输出流
OutputStream outputStream = response.getOutputStream();
// 创建文档
Document document = new Document(PageSize.A4);
// 在iText库中,PdfWriter类用于将PDF文件写入输出流或文件中。第一个参数是一个Document对象,用于表示要写入PDF文件的内容;第二个参数是一个OutputStream对象,用于表示PDF文件的输出目标,可以是一个文件输出流、内存输出流等
// PdfWriter.getInstance(document, outputStream);
/*
创建一个内存输出流,用于将PDF文件的内容输出到内存中(不用上边的文本流,用内存存储读取方式,是因为添加水印时,需要将PDF文件的内容读取到内存中,获取总共多少页,再加入)
注意,这种方式会将整个PDF文件的内容都读取到内存中,然后再写入到输出流中,可能会导致内存溢出或性能问题。因此,在处理大文件时,最好使用基于流的方式逐个页面地读取和写入PDF文件的内容。
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 创建一个PdfWriter对象,用于将PDF文件的内容输出到内存输出流中
PdfWriter writer = PdfWriter.getInstance(document, baos);
document.open();
// 创建表格 加表头(4列的表格)
PdfPTable table = new PdfPTable(4);
//设置表格宽度占页面宽度的百分比
table.setWidthPercentage(100);
// 设置文本方向为从左到右
table.setRunDirection(PdfWriter.RUN_DIRECTION_LTR);
// 在表格中添加单元格并设置字体
table.addCell(createCell("教师编号"));
table.addCell(createCell("教师名字"));
table.addCell(createCell("总分"));
table.addCell(createCell("是否推荐"));
for(GradeResultDto appraiseExportDataDto:GradeResultDtoList){
// 把数据传到appraiseExportDataDto中,先弄一下测试数据
/* appraiseExportDataDto.setTeacherId(1L);
appraiseExportDataDto.setTeacherName("sss");
appraiseExportDataDto.setScoresResult(BigDecimal.valueOf(90.222));
appraiseExportDataDto.setIsRecommend(true);*/
table.addCell(createCell(String.valueOf(appraiseExportDataDto.getTeacherId())));
table.addCell(createCell(String.valueOf(appraiseExportDataDto.getTeacherName())));
table.addCell(createCell(String.valueOf(appraiseExportDataDto.getScoresResult())));
// table.addCell(createCell(String.valueOf(appraiseExportDataDto.getIsRecommend())));
table.addCell(createCell(booleanToChinese(appraiseExportDataDto.getIsRecommend())));
}
// 将表格添加到文档中
document.add(table);
// 关闭文档
document.close();
writer.close();
String watermark =springWatermark;
try {
// 读取 PDF 文件并创建 PdfReader 对象
PdfReader reader = new PdfReader(baos.toByteArray());
// 创建一个文本水印
BaseFont font1 = BaseFont.createFont("ruoyi-admin/src/main/java/com/ruoyi/review/utils/fangsong.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
/* Font.NORMAL是iText中的一种字体样式,表示正常字体样式,也称为普通样式。它是iText的Font类中的四种字体样式之一,
其他样式包括Font.BOLD(加粗样式)、Font.ITALIC(斜体样式)和Font.UNDERLINE(下划线样式)。
使用Font.NORMAL设置字体样式时,不会应用任何加粗、倾斜或下划线效果,文本将显示为普通的正常字体样式。*/
Font font = new Font(font1, 30, Font.NORMAL);
Phrase phrase = new Phrase(watermark, font);
PdfContentByte canvas;
PdfGState gs = new PdfGState();
gs.setFillOpacity(0.1f);
gs.setStrokeOpacity(0.1f);
// 循环遍历每一页 PDF,向其添加水印
int totalPages = reader.getNumberOfPages(); // 获取PDF文件的总页数
PdfStamper stamper = new PdfStamper(reader, response.getOutputStream()); // 创建PdfStamper对象,用于在PDF文件上添加内容
for (int i = 1; i <= totalPages; i++) { // 遍历PDF文件的每一页
canvas = new PdfContentByte(stamper.getWriter()); // 创建PdfContentByte对象,用于在PDF文件上绘制内容
canvas = stamper.getUnderContent(i); // 获取当前页的PdfContentByte对象,并将内容添加到其下层
canvas.beginText(); // 开始绘制文本
canvas.setGState(gs); // 设置透明度
BaseColor watermarkColor = new BaseColor(0, 0, 0);
/* //上边的Font font = new Font(font1, 40, Font.NORMAL);覆盖了这里,所以字体大小和样式都没有起作用
canvas.setColorFill(watermarkColor); // 设置文本颜色 要在设置透明度之后
System.out.println(font+"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
canvas.setFontAndSize(font.getBaseFont(), 25); // 设置字体和字号 */
Rectangle rect = reader.getPageSizeWithRotation(i); // 获取当前页的大小
ColumnText.showTextAligned(canvas, Element.ALIGN_CENTER, phrase, rect.getWidth() / 2, rect.getHeight() / 2, 45); // 在PDF文件的当前页上居中绘制文本
canvas.endText(); // 结束绘制文本
}
// 关闭 PdfStamper 和 PdfReader 对象
stamper.close();
reader.close();
} catch (Exception e) {
throw new Exception("添加水印时出现异常:" + e.getMessage());
}
// 将内存输出流中的数据写入文件输出流中
outputStream.write(baos.toByteArray());
// 刷新和关闭输出流
outputStream.flush();
outputStream.close();
}
/*
iText 5 中默认支持了多种中文字体,包括:
STSong-Light:华文宋体
STHeiti-Light:华文黑体
STKaiti-Light:华文楷体
STZhongsong:华文中宋
FangSong:仿宋
*/
// 创建带有四个表头的单元格
private PdfPCell createCell(String text) throws DocumentException, IOException {
// 因为用设置默认的单元格 中文字体会爆红table.getDefaultCell().setFont(font); 没有setFont
// 所以中文没办法显示的解决办法:可以引入字体来显示 创建支持中文字符集的字体对象 BaseFont.createFont()方法来加载它们
BaseFont bfChinese =BaseFont.createFont("ruoyi-admin/src/main/java/com/ruoyi/review/utils/fangsong.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
Font fontChinese = new Font(bfChinese, 12, Font.NORMAL);
PdfPCell cell=new PdfPCell(new Phrase(text, fontChinese));
// 设置单元格的垂直对齐方式为居中
cell.setVerticalAlignment(com.itextpdf.text.Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(com.itextpdf.text.Element.ALIGN_CENTER);
//设置单元格是否使用上行和下行
cell.setUseAscender(true);
cell.setUseDescender(true);
//用于设置单元格的内边距
cell.setPadding(5);
// 设置单元格跨两列 (就是合并两个单元格)
// cell.setColspan(2);
// cell.setRowspan(2);
// 设置单元格的背景颜色
// cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
// 设置单元格的边框宽度和颜色
cell.setBorderWidth(1);
cell.setBorderColor(BaseColor.BLACK);
return cell;
}
//把布尔类型的转换成中文
public static String booleanToChinese(boolean value) {
return value ? "是" : "否";
}
}
但是上述代码如果部署到服务器上之后,会出现字体文件找不到的问题
“ruoyi-admin/src/main/java/com/ruoyi/review/utils/fangsong.ttf”(没有那个文件或目录)
原因:
如果文件不放在静态文件夹下面,且没有在 Spring Boot 项目中单独定义其他路径,那么在部署时通常不会被访问到。因为 Spring Boot 默认只会映射静态文件夹下的文件,如果要访问其他路径下的文件,需要在 Spring Boot 项目中进行配置。
解决办法:
1、把字体文件放在静态文件夹resources/fonts下
2、配置yml文件
spring:
itext:
fonts:
dir: classpath:/fonts
3、更改字体路径代码(两处,一个是表格中的文字,一个是水印中的文字)
BaseFont font1 = BaseFont.createFont("fonts/fangsong.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);