|
|
|
@ -0,0 +1,467 @@ |
|
|
|
package com.hxhq.business.utils; |
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode; |
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
import com.hxhq.business.domain.*; |
|
|
|
import com.hxhq.business.utils.lang.TemplateUtil; |
|
|
|
import com.hxhq.business.utils.pdf.PdfBaseUtil; |
|
|
|
import com.hxhq.business.utils.pdf.PdfExportUtil; |
|
|
|
import com.hxhq.common.core.text.Convert; |
|
|
|
import com.hxhq.common.core.utils.DateUtils; |
|
|
|
import com.hxhq.common.core.utils.StringUtils; |
|
|
|
import com.hxhq.common.security.utils.SecurityUtils; |
|
|
|
import com.hxhq.system.api.model.LoginUser; |
|
|
|
import com.itextpdf.text.*; |
|
|
|
import com.itextpdf.text.pdf.*; |
|
|
|
import org.slf4j.Logger; |
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
import org.springframework.stereotype.Component; |
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
import java.io.FileOutputStream; |
|
|
|
import java.io.IOException; |
|
|
|
import java.nio.file.Paths; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Random; |
|
|
|
import java.util.StringJoiner; |
|
|
|
|
|
|
|
/** |
|
|
|
* 试验方法pdf导出 |
|
|
|
* |
|
|
|
* @author HanLong |
|
|
|
*/ |
|
|
|
@Component |
|
|
|
public class StudyFormFillUtil { |
|
|
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(StudyFormFillUtil.class); |
|
|
|
public static String language = "zh"; |
|
|
|
/** |
|
|
|
* 资源映射路径 前缀 |
|
|
|
*/ |
|
|
|
@Value("${file.prefix}") |
|
|
|
public String localFilePrefix; |
|
|
|
|
|
|
|
/** |
|
|
|
* 域名或本机访问地址 |
|
|
|
*/ |
|
|
|
@Value("${file.domain}") |
|
|
|
public String domain; |
|
|
|
|
|
|
|
/** |
|
|
|
* 上传文件存储在本地的根路径 |
|
|
|
*/ |
|
|
|
@Value("${file.path}") |
|
|
|
private String localFilePath; |
|
|
|
|
|
|
|
/** |
|
|
|
* 复制原有PDF的所有页面到新Document中 |
|
|
|
*/ |
|
|
|
private static void copyOriginalPagesToDocument(Document doc, PdfWriter writer, PdfReader reader) throws DocumentException { |
|
|
|
int pageCount = reader.getNumberOfPages(); |
|
|
|
// 获取直接内容画布 |
|
|
|
PdfContentByte canvas = writer.getDirectContent(); |
|
|
|
|
|
|
|
for (int i = 1; i <= pageCount; i++) { |
|
|
|
doc.newPage(); // 新建一页(与原PDF页面一一对应) |
|
|
|
// 5.5.11中通过PdfImportedPage导入原有页面 |
|
|
|
PdfImportedPage importedPage = writer.getImportedPage(reader, i); |
|
|
|
// 将原有页面绘制到新文档的当前页 |
|
|
|
canvas.addTemplate(importedPage, 0, 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 导出table |
|
|
|
* |
|
|
|
* @param headerText |
|
|
|
* @param fileDir |
|
|
|
* @return |
|
|
|
*/ |
|
|
|
public static String export(String version,String lang, String headerText, String srcPdf, String fileDir, List<StudyFormFillJcgj> jcgjList, List<StudyFormFillQmxx> qmxxList) { |
|
|
|
language = lang; |
|
|
|
Document document = null; |
|
|
|
FileOutputStream fos = null; |
|
|
|
String filePath = ""; |
|
|
|
String fileName = ""; |
|
|
|
try { |
|
|
|
// 1. 生成文件名 |
|
|
|
fileName = generateFileName(); |
|
|
|
// 2. 确保目录存在 |
|
|
|
File dir = new File(fileDir); |
|
|
|
if (!dir.exists()) { |
|
|
|
dir.mkdirs(); |
|
|
|
} |
|
|
|
// 3. 完整文件路径 |
|
|
|
filePath = Paths.get(fileDir, fileName).toString(); |
|
|
|
// 创建PDF文档 设置文档边距,避免内容遮挡页眉页脚 |
|
|
|
// 1. 读取原有PDF,获取页面尺寸(保证新文档与原文档格式一致) |
|
|
|
PdfReader srcReader = new PdfReader(srcPdf); |
|
|
|
// 取第一页尺寸作为新文档尺寸 |
|
|
|
Rectangle pageSize = srcReader.getPageSize(1); |
|
|
|
// 2. 初始化Document(iText 5的核心高层类) |
|
|
|
document = new Document(pageSize); |
|
|
|
fos = new FileOutputStream(filePath); |
|
|
|
// 3. 关联PdfWriter,绑定Document和输出流(5.5.11必须先创建Writer再open Document) |
|
|
|
PdfWriter writer = PdfWriter.getInstance(document, fos); |
|
|
|
// 设置页面事件,每页添加文字页眉 |
|
|
|
String sign = "hxhq"; |
|
|
|
LoginUser loginUser = SecurityUtils.getLoginUser(); |
|
|
|
if (loginUser != null) { |
|
|
|
sign = loginUser.getSysUser().getNickName() + " " + DateUtils.getTime(); |
|
|
|
} |
|
|
|
writer.setPageEvent(new TextHeaderEvent(sign+" "+version, headerText, false)); |
|
|
|
// 4. 设置PDF属性 |
|
|
|
document.addTitle("华西海圻"); |
|
|
|
document.addAuthor("华西海圻"); |
|
|
|
document.addCreator("华西海圻"); |
|
|
|
document.addCreationDate(); |
|
|
|
document.open(); |
|
|
|
// 5. 复制原有PDF的所有页面到新Document中 |
|
|
|
copyOriginalPagesToDocument(document, writer, srcReader); |
|
|
|
// 7. 表格设置到新的一页 |
|
|
|
document.newPage(); |
|
|
|
addQmxx(document, qmxxList, lang); |
|
|
|
addJcgj(document, jcgjList, lang); |
|
|
|
} catch (Exception e) { |
|
|
|
throw new RuntimeException("生成失败: " + e.getMessage()); |
|
|
|
} finally { |
|
|
|
if (document != null) { |
|
|
|
document.close(); |
|
|
|
} |
|
|
|
if (fos != null) { |
|
|
|
try { |
|
|
|
fos.close(); |
|
|
|
} catch (IOException e) { |
|
|
|
log.error("关闭文件流失败", e); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return fileName; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 签名信息 |
|
|
|
* 申请表单 |
|
|
|
* |
|
|
|
* @param document |
|
|
|
* @param studyFormFillQmxxList |
|
|
|
* @throws IOException |
|
|
|
* @throws DocumentException |
|
|
|
*/ |
|
|
|
public static void addQmxx(Document document, List<StudyFormFillQmxx> studyFormFillQmxxList, String lang) throws IOException, DocumentException { |
|
|
|
language = lang; |
|
|
|
PdfBaseUtil.addUnderlinedTitle(document, getName("签名信息"), 10, true); |
|
|
|
// 9. 表头 |
|
|
|
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); |
|
|
|
Font headerFont = new Font(bfChinese, 10, Font.NORMAL); |
|
|
|
Font contentFont = new Font(bfChinese, 10, Font.NORMAL); |
|
|
|
|
|
|
|
// 8. 创建表格 |
|
|
|
PdfPTable table = new PdfPTable(4); |
|
|
|
table.setWidthPercentage(100); |
|
|
|
table.setSpacingBefore(10); |
|
|
|
String[] headers = {getName("签名人"), getName("签名意义"), getName("签名时间"), getName("备注原因")}; |
|
|
|
for (String header : headers) { |
|
|
|
PdfPCell cell = new PdfPCell(new Phrase(header, headerFont)); |
|
|
|
cell.setPadding(8); |
|
|
|
cell.setHorizontalAlignment(Element.ALIGN_CENTER); |
|
|
|
cell.setBackgroundColor(BaseColor.WHITE); |
|
|
|
cell.setBorderWidth(1); |
|
|
|
table.addCell(cell); |
|
|
|
} |
|
|
|
if (studyFormFillQmxxList.size() > 0) { |
|
|
|
int rowNum = 0; |
|
|
|
for (StudyFormFillQmxx studyFormFillQmxx : studyFormFillQmxxList) { |
|
|
|
// 交替行颜色 |
|
|
|
if (rowNum % 2 == 0) { |
|
|
|
table.getDefaultCell().setBackgroundColor(BaseColor.WHITE); |
|
|
|
} else { |
|
|
|
table.getDefaultCell().setBackgroundColor(BaseColor.WHITE); |
|
|
|
} |
|
|
|
table.addCell(PdfBaseUtil.createCell(studyFormFillQmxx.getQmrMc(), contentFont)); |
|
|
|
table.addCell(PdfBaseUtil.createCell("en".equals(language) ? studyFormFillQmxx.getQmyyEn() : studyFormFillQmxx.getQmyy(), contentFont)); |
|
|
|
table.addCell(PdfBaseUtil.createCell(PdfExportUtil.parseDateToStr(studyFormFillQmxx.getCreateTime()), contentFont)); |
|
|
|
table.addCell(PdfBaseUtil.createCell(studyFormFillQmxx.getRemark(), contentFont)); |
|
|
|
rowNum++; |
|
|
|
} |
|
|
|
} |
|
|
|
document.add(table); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 稽查轨迹 |
|
|
|
* 申请表单 |
|
|
|
* |
|
|
|
* @param document |
|
|
|
* @param studyFormFillJcgjList |
|
|
|
* @throws IOException |
|
|
|
* @throws DocumentException |
|
|
|
*/ |
|
|
|
public static void addJcgj(Document document, List<StudyFormFillJcgj> studyFormFillJcgjList, String lang) throws IOException, DocumentException { |
|
|
|
language = lang; |
|
|
|
PdfBaseUtil.addUnderlinedTitle(document, getName("稽查轨迹"), 10, true); |
|
|
|
for (StudyFormFillJcgj jcgj : studyFormFillJcgjList) { |
|
|
|
StringJoiner result = new StringJoiner(", "); |
|
|
|
if ("en".equals(lang)) { |
|
|
|
PdfBaseUtil.addUnderlinedTitle(document, PdfExportUtil.parseDateToStr(jcgj.getCreateTime()) + " " + jcgj.getJcmcEn(), 10, false); |
|
|
|
if (StringUtils.isNoneBlank(jcgj.getJcnrEn())) { |
|
|
|
ObjectMapper mapper = new ObjectMapper(); |
|
|
|
JsonNode jsonArray = mapper.readTree(jcgj.getJcnrEn()); |
|
|
|
for (JsonNode node : jsonArray) { |
|
|
|
if (node.get("name") != null) { |
|
|
|
String name = node.get("name").asText(); |
|
|
|
String value = node.get("value") != null ? node.get("value").asText() : ""; |
|
|
|
result.add(name + ":" + value); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
PdfBaseUtil.addUnderlinedTitle(document, PdfExportUtil.parseDateToStr(jcgj.getCreateTime()) + " " + jcgj.getJcmc(), 10, false); |
|
|
|
if (StringUtils.isNoneBlank(jcgj.getJcnr())) { |
|
|
|
ObjectMapper mapper = new ObjectMapper(); |
|
|
|
JsonNode jsonArray = mapper.readTree(jcgj.getJcnr()); |
|
|
|
for (JsonNode node : jsonArray) { |
|
|
|
if (node.get("name") != null) { |
|
|
|
String name = node.get("name").asText(); |
|
|
|
String value = node.get("value") != null ? node.get("value").asText() : ""; |
|
|
|
result.add(name + ":" + value); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (StringUtils.isNoneBlank(jcgj.getRemark())) { |
|
|
|
result.add(getName("备注") + ":" + jcgj.getRemark()); |
|
|
|
} |
|
|
|
if (StringUtils.isNoneBlank(jcgj.getQmrMc())) { |
|
|
|
result.add(getName("签名人") + ":" + jcgj.getQmrMc()); |
|
|
|
} |
|
|
|
PdfBaseUtil.addUnderlinedTitle(document, result.toString(), 10, false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取名称 |
|
|
|
* |
|
|
|
* @param name |
|
|
|
* @return |
|
|
|
*/ |
|
|
|
public static String getName(String name) { |
|
|
|
return "en".equals(language) ? TemplateUtil.getEn(name) : name; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 创建表格 |
|
|
|
*/ |
|
|
|
private static PdfPCell createCell(String content, Font font) { |
|
|
|
PdfPCell cell = new PdfPCell(new Phrase(content, font)); |
|
|
|
cell.setHorizontalAlignment(Element.ALIGN_CENTER); |
|
|
|
cell.setVerticalAlignment(Element.ALIGN_MIDDLE); |
|
|
|
cell.setPadding(6); |
|
|
|
cell.setMinimumHeight(25); |
|
|
|
return cell; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 生成文件名 |
|
|
|
*/ |
|
|
|
private static String generateFileName() { |
|
|
|
Random random = new Random(); |
|
|
|
String timestamp = DateUtils.dateTimeNow("yyyyMMddHHmmss"); |
|
|
|
return String.format("%s_%s.pdf", timestamp, random.nextInt(1000)); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 页面事件处理类 - 每页添加文字页眉 |
|
|
|
*/ |
|
|
|
static class TextHeaderEvent extends PdfPageEventHelper { |
|
|
|
private Font footerFont; |
|
|
|
private String signText; |
|
|
|
private String headerText; |
|
|
|
private BaseFont baseFont; |
|
|
|
private Boolean showHaderLine = true; |
|
|
|
private int totalPages = 0; |
|
|
|
private PdfTemplate total; |
|
|
|
|
|
|
|
public TextHeaderEvent(String signText, String headerText, Boolean showHaderLine) throws Exception { |
|
|
|
try { |
|
|
|
this.showHaderLine = showHaderLine; |
|
|
|
this.signText = signText; |
|
|
|
this.headerText = headerText; |
|
|
|
// 创建字体(支持中文) |
|
|
|
this.baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); |
|
|
|
footerFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.GRAY); |
|
|
|
} catch (Exception e) { |
|
|
|
footerFont = new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL, BaseColor.GRAY); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onOpenDocument(PdfWriter writer, Document document) { |
|
|
|
total = writer.getDirectContent().createTemplate(100, 16); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onCloseDocument(PdfWriter writer, Document document) { |
|
|
|
totalPages = writer.getPageNumber(); |
|
|
|
String totalPagesStr = String.valueOf(totalPages); |
|
|
|
|
|
|
|
// 重新创建合适宽度的模板 |
|
|
|
BaseFont baseFont = footerFont.getBaseFont(); |
|
|
|
float actualWidth = baseFont.getWidthPoint(totalPagesStr, footerFont.getSize()); |
|
|
|
|
|
|
|
total.beginText(); |
|
|
|
total.setFontAndSize(baseFont, footerFont.getSize()); |
|
|
|
total.setTextMatrix(0, 0); |
|
|
|
total.showText(totalPagesStr); |
|
|
|
total.endText(); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onEndPage(PdfWriter writer, Document document) { |
|
|
|
try { |
|
|
|
PdfContentByte cb = writer.getDirectContent(); |
|
|
|
|
|
|
|
// 获取页面尺寸 |
|
|
|
Rectangle pageSize = document.getPageSize(); |
|
|
|
float pageWidth = pageSize.getWidth(); |
|
|
|
float pageHeight = pageSize.getHeight(); |
|
|
|
|
|
|
|
// 设置页眉参数 |
|
|
|
float topMargin = 15; // 顶部边距 |
|
|
|
float fontSize = 12; // 字体大小 |
|
|
|
|
|
|
|
// 计算文字宽度(用于居中) |
|
|
|
float textWidth = baseFont.getWidthPoint(signText, fontSize); |
|
|
|
|
|
|
|
// 计算居中位置 |
|
|
|
float textX = (pageSize.getLeft() + pageSize.getRight()) / 2; |
|
|
|
float textY = pageHeight - topMargin; |
|
|
|
|
|
|
|
// 获取画布 |
|
|
|
PdfContentByte canvas = writer.getDirectContent(); |
|
|
|
|
|
|
|
int currentPage = writer.getPageNumber(); |
|
|
|
float y = document.bottom() - 20; |
|
|
|
|
|
|
|
// 在每一页都重新计算,确保位置准确 |
|
|
|
String pageText = signText+ " 第 " + currentPage + " 页 / 共 "; |
|
|
|
BaseFont baseFont = footerFont.getBaseFont(); |
|
|
|
|
|
|
|
// 计算页面文本宽度 |
|
|
|
float pageTextWidth = baseFont.getWidthPoint(pageText, footerFont.getSize()); |
|
|
|
|
|
|
|
// 临时用估计的总页数计算位置(等文档关闭后会被替换) |
|
|
|
String tempTotal = String.valueOf(writer.getPageNumber() + 5); // 估计值 |
|
|
|
float totalWidth = baseFont.getWidthPoint(tempTotal, footerFont.getSize()); |
|
|
|
|
|
|
|
// 计算起始位置 |
|
|
|
float totalTextWidth = pageTextWidth + totalWidth + |
|
|
|
baseFont.getWidthPoint(" 页", footerFont.getSize()); |
|
|
|
float startX = (document.getPageSize().getWidth() - totalTextWidth) / 2; |
|
|
|
|
|
|
|
// 写入当前页信息 |
|
|
|
ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, |
|
|
|
new Phrase(pageText, footerFont), startX, y, 0); |
|
|
|
|
|
|
|
// 添加总页数模板 |
|
|
|
cb.addTemplate(total, startX + pageTextWidth, y); |
|
|
|
|
|
|
|
// 写入"页"字 |
|
|
|
ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, |
|
|
|
new Phrase(" 页", footerFont), startX + pageTextWidth + totalWidth, y, 0); |
|
|
|
|
|
|
|
// PdfContentByte content = writer.getDirectContentUnder(); // 在水印层添加 |
|
|
|
// |
|
|
|
// // 设置透明度 |
|
|
|
// PdfGState gs = new PdfGState(); |
|
|
|
// float opacity = Convert.toFloat("0.3"); |
|
|
|
// gs.setFillOpacity(opacity); |
|
|
|
// content.setGState(gs); |
|
|
|
// |
|
|
|
// // 设置字体和颜色 |
|
|
|
// content.setColorFill(BaseColor.RED); |
|
|
|
// content.setFontAndSize(baseFont, fontSize); |
|
|
|
// |
|
|
|
// float width = pageSize.getWidth(); |
|
|
|
// float height = pageSize.getHeight(); |
|
|
|
// |
|
|
|
// // 重复铺满水印 |
|
|
|
// addRepeatedWatermark(content, width, height, signText); |
|
|
|
// 添加页眉文字 |
|
|
|
if (StringUtils.isNoneBlank(headerText)) { |
|
|
|
PdfContentByte cb2 = writer.getDirectContent(); |
|
|
|
ColumnText.showTextAligned(cb2, Element.ALIGN_LEFT, |
|
|
|
new Phrase(headerText, footerFont), startX, textY, 0); |
|
|
|
// canvas.beginText(); |
|
|
|
// canvas.setFontAndSize(baseFont, fontSize); |
|
|
|
// canvas.setTextMatrix(textX, textY); |
|
|
|
// canvas.showText(headerText); |
|
|
|
// canvas.endText(); |
|
|
|
if (showHaderLine) { |
|
|
|
// 添加页眉分隔线(可选) |
|
|
|
addHeaderLine(canvas, pageWidth, textY - 10); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
e.printStackTrace(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 添加水印 |
|
|
|
* |
|
|
|
* @param content |
|
|
|
* @param width |
|
|
|
* @param height |
|
|
|
* @param signText |
|
|
|
*/ |
|
|
|
private void addRepeatedWatermark(PdfContentByte content, float width, float height, String signText) { |
|
|
|
// 计算文字尺寸 |
|
|
|
float fontSize = Convert.toFloat("10"); |
|
|
|
float textWidth = baseFont.getWidthPoint(signText, fontSize) + 50; |
|
|
|
float textHeight = baseFont.getAscentPoint(signText, fontSize) - |
|
|
|
baseFont.getDescentPoint(signText, fontSize); |
|
|
|
|
|
|
|
// 计算间距 |
|
|
|
float stepX = textWidth + 70; |
|
|
|
float stepY = textHeight + 100; |
|
|
|
|
|
|
|
// 添加重复水印 |
|
|
|
Integer two = 2; |
|
|
|
for (float x = textWidth / two; x < width; x += stepX) { |
|
|
|
for (float y = textHeight / two; y < height; y += stepY) { |
|
|
|
content.beginText(); |
|
|
|
content.showTextAligned( |
|
|
|
Element.ALIGN_CENTER, |
|
|
|
signText, |
|
|
|
x, |
|
|
|
y, |
|
|
|
fontSize |
|
|
|
); |
|
|
|
content.endText(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 添加页眉线 |
|
|
|
* |
|
|
|
* @param canvas |
|
|
|
* @param pageWidth |
|
|
|
* @param yPos |
|
|
|
*/ |
|
|
|
private void addHeaderLine(PdfContentByte canvas, float pageWidth, float yPos) { |
|
|
|
canvas.setLineWidth(0.5f); |
|
|
|
canvas.lineTo(pageWidth, yPos); |
|
|
|
canvas.stroke(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |