pdf导出
1.pdf导出用到的依赖
<properties>
<freemaker.version>2.3.20</freemaker.version>
<itextpdf.version>5.5.11</itextpdf.version>
<flying-saucer-pdf.version>9.1.5</flying-saucer-pdf.version>
<pdfbox.version>2.0.1</pdfbox.version>
</properties>
<dependencies>
<!--freemaker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freemaker.version}</version>
</dependency>
<!-- itext:转PDF -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<!-- xhtmlrenderer:html转pdf时支持css特性 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>${flying-saucer-pdf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>${pdfbox.version}</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox-app</artifactId>
<version>${pdfbox.version}</version>
</dependency>
2.准备模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>
body {
font-family: "SimHei";
text-align: center;
}
.out-box {
display: flex;
flex-direction: column;
align-items: center;
}
.out-box > div + div {
margin-top: 100px;
}
.id {
display: flex;
justify-content: flex-end;
margin-right: 150px;
}
img {
height: 100%;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
/* transform: rotate(-90deg); */
}
table {
width: 100%;
margin: 0 auto;
border-collapse: collapse;
}
th,
td {
height: 32px;
min-height: 32px;
border: 1px solid #000000;
text-align: center;
}
.leftAlign {
text-align: left;
}
.textalign-left {
text-align: start;
}
#table_border1 {
width: 690px;
display: inline-block;
line-height: 26px;
/* border: 1px solid #000000; */
}
#table_border2 {
width: 690px;
display: inline-block;
position: relative;
}
.font-bold {
font-weight: bold;
}
/* 首行缩进两个空格 */
.lineIndented {
display: inline-block;
width: 2em;
/* text-indent: 2em; */
}
/** 文本下划线 */
.text-underline {
position: relative;
display: inline-block;
text-align: center;
}
.text-underline:after {
position: absolute;
bottom: 0px;
left: 0px;
height: 1px;
width: 100%;
content: "";
background-color: #000;
}
.img-box {
height: 100px;
}
</style>
</head>
<body>
<div class="out-box">
<div id="table_border2">
<h2>表1-2 食品销售监督检查要点表</h2>
<div style="text-align: left;">
<div>食品通用检查项目:重点项(*)38项,一般项41项,共79项。</div>
<div>食品其他检查项目:重点项(*)12项,一般项5项,共17项。</div>
<div>相关主体检查项目:重点项(*)6项,一般项9项,共15项。</div>
</div>
<table>
<colgroup>
<col span="1" style="width: 10%;"/>
<col span="1" style="width: 7%;"/>
<col span="1" style="width: 55%;"/>
<col span="1" style="width: 10%;"/>
<col span="1" style="width: 19%;"/>
</colgroup>
<tbody>
<!-- 标题 -->
<tr>
<th colspan="5">食品通用检查项目(79项)</th>
</tr>
<tr>
<th>检查项目</th>
<th>序号</th>
<th>检 查 内 容</th>
<th>评价</th>
<th>备注</th>
</tr>
<!-- 跨多少列 -->
<#list subjectNames1 as subject>
<#list items as item>
<tr>
<#if item_index == 0>
<td rowspan="${items?size}">${subject!}</td>
</#if>
<#if subject=="${item.subjectNumber}.${item.subjectName}">
<td>${item.subjectNumber!""}.${item.number!""}</td>
<td class="textalign-left">
<#if subject=="${item.subjectNumber}.${item.subjectName}">
${item.itemContent!}
</#if>
</td>
<td class="textalign-left">
<input style="margin-left: 15px" type="checkbox"
checked="<#if item.result.code=="2"> checked</#if>"/>是
<input style="margin-left: 15px" type="checkbox"
checked="<#if item.result.code=="1"> checked</#if>"/>否
</td>
<td class="textalign-left">
<#if item.result.code == "0">
合理缺项
</#if>
</td>
</#if>
</tr>
</#list>
</#list>
<!-- 标题 -->
<tr>
<th colspan="5">食品其他检查项目(17项)</th>
</tr>
<tr>
<th>检查项目</th>
<th>序号</th>
<th>检 查 内 容</th>
<th>评价</th>
<th>备注</th>
</tr>
<#list subjectNames2 as subject>
<#list items as item>
<tr>
<#if item_index == 0>
<td rowspan="${items?size}">${subject!}</td>
</#if>
<#if subject=="${item.subjectNumber}.${item.subjectName}">
<td>${item.subjectNumber!""}.${item.number!""}</td>
<td class="textalign-left">
<#if subject=="${item.subjectNumber}.${item.subjectName}">
${item.itemContent!}
</#if>
</td>
<td class="textalign-left">
<input style="margin-left: 15px" type="checkbox"
checked="<#if item.result.code=="2"> checked</#if>"/>是
<input style="margin-left: 15px" type="checkbox"
checked="<#if item.result.code=="1"> checked</#if>"/>否
</td>
<td class="textalign-left">
<#if item.result.code == "0">
合理缺项
</#if>
</td>
</#if>
</tr>
</#list>
</#list>
<!-- 标题 -->
<tr>
<th colspan="5">相关主体检查项目(9项)</th>
</tr>
<tr>
<th>检查项目</th>
<th>序号</th>
<th>检 查 内 容</th>
<th>评价</th>
<th>备注</th>
</tr>
<#list subjectNames3 as subject>
<#list items as item>
<tr>
<#if item_index == 0>
<td rowspan="${items?size}">${subject!}</td>
</#if>
<#if subject=="${item.subjectNumber}.${item.subjectName}">
<td>${item.subjectNumber}.${item.number!""}</td>
<td class="textalign-left">
<#if subject=="${item.subjectNumber}.${item.subjectName}">
${item.itemContent!}
</#if>
</td>
<td class="textalign-left">
<input style="margin-left: 15px" type="checkbox"
checked="<#if item.result.code=="2"> checked</#if>"/>是
<input style="margin-left: 15px" type="checkbox"
checked="<#if item.result.code=="1"> checked</#if>"/>否
</td>
<td class="textalign-left">
<#if item.result.code == "0">
合理缺项
</#if>
</td>
</#if>
</tr>
</#list>
</#list>
<tr>
<td colspan="5" style="text-align: left;">其他需要记录的问题:<br/><br/><br/><br/></td>
</tr>
</tbody>
</table>
<div>
<div>说明:1.如果检查项目存在合理缺项,该项无需勾选“是”与“否”,并在备注中说明,不计入否项数。</div>
<div style="text-indent:3em">2.检查具体要求可参考《食品销售安全监督检查指南》《特殊食品安全销售监督检查指南》。</div>
</div>
</div>
<div id="table_border1">
<h2>表2-2 食品经营监督检查结果记录表</h2>
<div class="id">编号:${recordId!""}</div>
<table>
<colgroup>
<col span="1" style="width: 17%"/>
<col span="1" style="width: 33%"/>
<col span="1" style="width: 17%"/>
<col span="1" style="width: 33%"/>
</colgroup>
<tbody>
<tr>
<td>被检查单位名称</td>
<td>${targetName!""}</td>
<td>经营地址</td>
<td>${targetAdderss!""}</td>
</tr>
<tr>
<td>联系人</td>
<td>${targetContacts!""}</td>
<td>联系方式</td>
<td>${targetContactsTel!""}</td>
</tr>
<tr>
<td>许可证编号或者备案编号</td>
<td>${targetLegalEntityUscCode!""}</td>
<td>检查次数</td>
<td>本年度第<span> ${targetInspectNumber!""} </span>次检查</td>
</tr>
<tr>
<td colspan="4" style="padding: 10px;">
<div class="leftAlign">检查内容:<br/>
<span class="lineIndented"></span>
<span class="text-underline"
style="padding: 0px 8px;">${executorTenantName!""}</span>检查人员
<span
class="text-underline"
style="min-width: 140px;">${executorName!""}</span>根据《中华人民共和国食品安全法》及其实施条例、《反食品浪费法》《未成年人保护法》,以及《食品生产经营监督检查管理办法》等规定,于
<span class="text-underline" style="min-width: 70px;">${beginTimeYear!""}</span>年
<span class="text-underline" style="min-width: 65px;">${beginTimeMonth!""}</span>月
<span class="text-underline" style="min-width: 65px;">${beginTimeDay!""}</span>日至
<span class="text-underline" style="min-width: 70px;">${endTimeYear!""}</span>年
<span class="text-underline" style="min-width: 65px;">${endTimeMonth!""}</span>月
<span class="text-underline" style="min-width: 65px;">${endTimeDay!""}</span>日,对你单位进行了
<input type="checkbox" checked="checked"/>日常检查
<input type="checkbox"/>飞行检查
<input type="checkbox"/>体系检查。本次监督依据
<input type="checkbox" checked="checked"/>食品销售监督检查要点表
<input type="checkbox"/>餐饮服务监督检查要点表
<input type="checkbox"/>其他:<span class="text-underline"
style="min-width: 120px;"></span> 。
共检查了( ${inspectItemCount!""} )项内容,其中重点项( ${inspectPointItemCount!""}
)项,一般项( ${inspectGeneralItemCount!""} )项。
</div>
</td>
</tr>
<tr>
<td colspan="4" class="leftAlign" style="padding: 10px;">
<div>
<span class="font-bold">检查结果: </span>本次检查发现不符合项( ${inspectItemDisqualificationCount!""} )项,其中:
<div>
<span class="lineIndented"></span>重点项( ${inspectPointItemUnqualifiedCount!""}
)项需要重点跟踪整改,项目序号及具体内容如下:<br/>
<#if point?? && (point?size > 0)>
<#list point as subject>
<p>${subject!''}</p>
</#list>
</#if>
<br/><br/>
<span class="lineIndented"></span>一般项( ${inspectGeneralItemUnqualifiedCount!""}
)项,项目序号分别是:<br/>
<#if general?? && (general?size > 0)>
<#list general as subject>
<p>${subject!''}</p>
</#list>
</#if>
</div>
</div>
<div>
<div class="font-bold">结果处理:</div>
<input type="checkbox" checked="<#if inspectResultHandle=="0"> checked</#if>"/>此次检查未发现违法违规行为和风险隐患问题;<br/>
<input type="checkbox"
checked="<#if inspectResultHandle??&&inspectResultHandle=="1"> checked</#if>"/>此次检查发现不符合监管要点表一般项目,存在轻微风险隐患,不涉及违法行为,责令当场改正,且已整改到位;<br/>
<input type="checkbox"
checked="<#if inspectResultHandle??&&inspectResultHandle=="3"> checked</#if>"/>此次检查发现不符合监督检查要点表相关项目,存在轻微违法违规行为,实施管易程序行政处罚,并做出责令限期整改决定;<br/>
<input type="checkbox"
checked="<#if inspectResultHandle??&&inspectResultHandle=="4"> checked</#if>"/>此次检查发现不符合监督检查要点表相关项目,涉嫌违法违规,建议立案查处。<br/>
<div>
<span class="font-bold">说明</span>(可附页):
</div>
</div>
</td>
</tr>
<tr>
<td colspan="2" class="leftAlign" style="padding: 10px;">
<div>
<span
style="display: inline-block; vertical-align: top;margin-bottom: 10px;">检查人员(签名):</span>
<#if inspectSignUrl??>
<img class="img-box" src="${downloadUrlPrefix!}${inspectSignUrl!}"/>
</#if>
</div>
<div>
<span style="display: inline-block; vertical-align: top;margin: 10px 0;">检查(执法)证件编号:${certificateNumber!""}</span>
</div>
<div style="text-align: end;"> ${beginTimeYear!""} 年 ${beginTimeMonth!""} 月 ${beginTimeDay!""}日
</div>
</td>
<td colspan="2" class="leftAlign" style="padding: 10px;">
<div>被检查单位意见:${targetOpinion!""}</div>
<div>
<span style="display: inline-block; vertical-align: top;margin: 10px 0;">法人或负责人:</span>
<#if beInspectSignUrl??>
<img class="img-box" src="${downloadUrlPrefix!}${beInspectSignUrl!}"/>
</#if>
</div>
<div style="text-align: end;"> ${endTimeYear!""} 年 ${endTimeMonth!""} 月 ${endTimeDay!""} 日(章)</div>
</td>
</tr>
</tbody>
</table>
<div style="text-align: left;margin-left: 50px;">
备注:已提醒食品经营者落实《安全生产法》主体责任义务
</div>
</div>
</div>
</body>
</html>
3.准备工具类
RiskPDFUtils,
package com.seerbigdata.ccisp.inspect.support;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.itextpdf.text.pdf.BaseFont;
import com.lowagie.text.DocumentException;
import com.seerbigdata.ccisp.core.constant.CcispConstant;
import com.seerbigdata.ccisp.core.constant.FileTypeConstants;
import com.seerbigdata.common.core.exception.GlobalException;
import com.seerbigdata.common.core.exception.InvalidOperateException;
import com.seerbigdata.common.utils.support.FileToMultipartFileUtils;
import com.seerbigdata.sr.common.file.pojo.FileInfoDTO;
import com.seerbigdata.sr.common.file.toolkit.SrFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* 电子表单 工具类
*
* @version 1.0
* @Author yuanlinquan@seerbigdata.com
* @Date 2022/06/25/9:46
*/
@Slf4j
@Configuration
public class RiskPDFUtils implements ApplicationContextAware {
/**
* render
*/
private static final ITextRenderer RENDER = new ITextRenderer();
/**
* pdf字体路径
*/
private static final String FONT = "font/chinese-simhei.ttf";
/**
* 单文件上传(saas多文件上传不可用)
*/
private final static String SINGLE_UPLOAD_FIELD_NAME = "file";
/**
* ApplicationContext 应用上下文
*/
private static ApplicationContext applicationContext;
/**
* 文件管理
*/
// private static RemoteFileInfoService fileInfoService;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
RiskPDFUtils.applicationContext = applicationContext;
}
/**
* check and init
*/
static void checkAndInit() {
// if (Objects.isNull(fileInfoService)) {
// fileInfoService = applicationContext.getBean(RemoteFileInfoService.class);
// }
}
static {
ClassPathResource fontResource = new ClassPathResource(FONT);
try {
RENDER.getFontResolver().addFont(fontResource.getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} catch (DocumentException | IOException e) {
log.error("生成接收文书文件,EformUtils静态代码块加载字体配置出错: [fontResource:{}] [{error:{}}]", fontResource, e.getMessage());
throw new GlobalException("系统异常,请稍后重试");
}
}
/**
* 生成电子表单
*
* @param ftlPath ftl文件名(不需要写后缀)
* @param objectMap 填充模板的参数
* @param relateId 上传文件时的关联编号
* @param pdfName 自定义PDF文件名(不需要写)
* @param isRemovePdfFirstPage 生成过程中是否需要删除pdf的第一页(如果不止一页的话)(遇到PDF样式有问题可选此项)
* @return : 返回生成的单个PDF的文件编号
*/
public static String generateEformWithFtlTemplate(
@NonNull String ftlPath,
@NonNull Map<String, Object> objectMap,
@Nullable String relateId,
@Nullable String pdfName,
@Nullable Boolean isRemovePdfFirstPage,
HttpServletResponse response) {
checkAndInit();
//1.pdf文件名
if (StrUtil.isBlank(pdfName)) {
pdfName = FileTypeConstants.PDF;
}
//2.pdf删除首页标记
if (Objects.isNull(isRemovePdfFirstPage)) {
isRemovePdfFirstPage = false;
}
//3.定义返回结果
String resultFileId = null;
FileInfoDTO pdfFile = null;
//4.定义需要用到的临时html文件、临时pdf文件、表单对象
Path htmlTempPath = null;
Path pdfTempPath = null;
FileItem pdfFileItem = null;
//5.生成表单并上传
//5.1.html模板路径
String templatePath = FreeMakerUtils.templateFilePath(ftlPath);
try {
//5.2 构建html临时文件
htmlTempPath = Files.createTempFile(FileTypeConstants.HTML, FileToMultipartFileUtils.buildFileSuffix(FileTypeConstants.HTML));
File htmlTempFile = htmlTempPath.toFile();
//5.3 填充模板并写入临时html文件
FreeMakerUtils.writer(objectMap, templatePath, htmlTempFile);
String htmlContent = FileUtil.readString(htmlTempFile, CharsetUtil.UTF_8);
//5.4 构建pdf临时文件
pdfTempPath = Files.createTempFile(pdfName, FileToMultipartFileUtils.buildFileSuffix(FileTypeConstants.PDF));
File pdfTempFile = pdfTempPath.toFile();
//5.5 html转pdf并写入pdf临时文件
htmlToPdf(htmlContent, pdfTempFile, isRemovePdfFirstPage);
//5.6 构建pdf上传文件表单实例
pdfFileItem = new DiskFileItemFactory().createItem(SINGLE_UPLOAD_FIELD_NAME, MediaType.APPLICATION_PDF_VALUE, true, pdfName.concat(FileToMultipartFileUtils.buildFileSuffix(FileTypeConstants.PDF)));
MultipartFile pdfMultipart = FileToMultipartFileUtils.fileToMultipartFile(pdfTempFile, pdfFileItem);
//5.7 上传pdf文件
pdfFile = SrFileUtil.upload(pdfMultipart, relateId, CcispConstant.PROJECT_NAME,CcispConstant.PROJECT_NAME);
// try(final InputStream inputStream = pdfMultipart.getInputStream()) {
// response.setContentType(pdfMultipart.getContentType());
// response.setContentLengthLong(pdfMultipart.getSize());
// String fileName = StringUtils.hasText(pdfMultipart.getOriginalFilename()) ? StringEscapeUtils.escapeJava(pdfMultipart.getOriginalFilename()) : pdfMultipart.getName();
// response.setHeader("Content-Disposition", "attachment; filename=".concat(fileName));
// ServletOutputStream outputStream = response.getOutputStream();
// IOUtils.copy(inputStream, outputStream);
// } catch (Exception e) {
// throw new InvalidOperateException("下载文件失败!");
// }
} catch (Exception e) {
log.error("生成pdf文件或图片文件失败: {}", e.getMessage());
} finally {
//6.1 释放资源
deleteFileItem(pdfFileItem);
deletePath(pdfTempPath);
deletePath(htmlTempPath);
}
//6.2 PDF生成成功
if (Objects.nonNull(pdfFile)){
resultFileId=pdfFile.getId();
}
return resultFileId;
}
/**
* html转pdf
*
* @param htmlContent html文件字符流
* @param pdfFile pdf文件
* @param isRemoveFirstPage 是否删除第一页(若样式导出有问题,可使用此标记)
*/
public static void htmlToPdf(String htmlContent, File pdfFile, boolean isRemoveFirstPage) throws DocumentException, IOException {
htmlToPdf(htmlContent, pdfFile);
if (!isRemoveFirstPage) {
return;
}
PdfUtils.removePdfFirstPage(pdfFile);
}
/**
* html转pdf
*
* @param htmlContent HTML文本内容
* @param pdfFile PDF File
* @author : zhangzhiqin / 2022-03-08
*/
private static void htmlToPdf(String htmlContent, File pdfFile) throws DocumentException, IOException {
ClassPathResource fontResource = new ClassPathResource(FONT);
ITextRenderer renderer = new ITextRenderer();
try {
renderer.getFontResolver().addFont(fontResource.getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} catch (DocumentException | IOException e) {
log.error("生成接收文书文件,EformUtils静态代码块加载字体配置出错: [fontResource:{}] [{error:{}}]", fontResource, e.getMessage());
throw new GlobalException("系统异常,请稍后重试");
}
// 解析html生成pdf
renderer.setDocumentFromString(htmlContent);
renderer.layout();
try (FileOutputStream fos = new FileOutputStream(pdfFile)) {
renderer.createPDF(fos);
}
}
/**
* delete file item
*
* @param fileItemList 文件项集合
*/
private static void deleteFileItem(List<FileItem> fileItemList) {
if (CollUtil.isEmpty(fileItemList)) {
return;
}
fileItemList.forEach(RiskPDFUtils::deleteFileItem);
}
/**
* delete file item
*
* @param fileItem 文件项集合
*/
private static void deleteFileItem(FileItem fileItem) {
if (Objects.nonNull(fileItem)) {
fileItem.delete();
}
}
/**
* delete path
*
* @param pathList 文件路径集合
*/
private static void deletePath(List<Path> pathList) {
if (CollUtil.isEmpty(pathList)) {
return;
}
pathList.forEach(RiskPDFUtils::deletePath);
}
/**
* delete path
*
* @param path 文件路径
*/
private static void deletePath(Path path) {
if (Objects.nonNull(path)) {
try {
Files.delete(path);
} catch (IOException e) {
log.error("Files.delete(path)失败 -- path:{} message:{}", path.getFileName(), e.getMessage());
}
}
}
}
PdfUtils,
package com.seerbigdata.ccisp.inspect.support;
import com.seerbigdata.ccisp.core.constant.FileTypeConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* pdf 工具类
*
* @author zhangzhiqin / zhangzhiqin@seerbigdata.com
* @version 1.0
* @since 2021-01-14
*/
@Slf4j
public class PdfUtils {
/**
* 转图片用,dpi越大转换后越清晰,相对转换速度越慢
*/
private static final Integer DPI = 200;
/**
* 删除pdf的第一页(有特殊需求的话)
*
* @param pdf
* @throws IOException
* @author zhangzhiqin / 2022-03-08
*/
public static void removePdfFirstPage(File pdf) throws IOException {
try(PDDocument document = PDDocument.load(pdf)) {
int totalPages = document.getNumberOfPages();
if (totalPages <= 1) {
//小于一页
return;
}
//大于一页
document.removePage(0);
document.save(pdf);
}
}
/**
* PDF转图片
*
* @param fileContent PDF文件的二进制流
* @return 单张以byte数组表示的图片集合
* @throws IOException
* @author zhangzhiqin / 2022-03-08
*/
public static List<byte[]> pdfToImage(byte[] fileContent) throws IOException {
return pdfToImage(fileContent, DPI);
}
/**
* PDF转图片
*
* @param pdfContent PDF文件信息
* @param dpi dpi越大转换后越清晰,相对转换速度越慢
* @return 单张以byte数组表示的图片集合
* @throws IOException
* @author zhangzhiqin / 2022-03-08
*/
public static List<byte[]> pdfToImage(byte[] pdfContent, int dpi) throws IOException {
List<byte[]> result = new ArrayList<>();
try (PDDocument document = PDDocument.load(pdfContent)) {
PDFRenderer renderer = new PDFRenderer(document);
for (int i = 0; i < document.getNumberOfPages(); ++i) {
BufferedImage bufferedImage = renderer.renderImageWithDPI(i, dpi);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, FileTypeConstants.PDF, out);
result.add(out.toByteArray());
}
}
return result;
}
}
FreeMakerUtils,
package com.seerbigdata.ccisp.inspect.support;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.seerbigdata.ccisp.core.constant.FileTypeConstants;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Map;
/**
* freemaker 工具类
*
* @author zhangzhiqin / zhangzhiqin@seerbigdata.com
* @version 1.0
* @since 2021-01-14
*/
@Slf4j
public class FreeMakerUtils {
/**
* Configuration
*/
private static Configuration configuration;
static {
configuration = new Configuration();
configuration.setDefaultEncoding(CharsetUtil.UTF_8);
configuration.setClassForTemplateLoading(FreeMakerUtils.class, StrUtil.SLASH);
}
/**
* freemaker动态填充html模板
*
* @param objectMap freemaker填充参数
* @param templatePath 模板路径
* @param htmlFile html文件
* @throws Exception
*/
public static void writer(Map<String, Object> objectMap, String templatePath, File htmlFile) throws Exception {
// 清除缓存
configuration.clearTemplateCache();
Template template = configuration.getTemplate(templatePath);
try (FileOutputStream fileOutputStream = new FileOutputStream(htmlFile)) {
template.process(objectMap, new OutputStreamWriter(fileOutputStream));
}
}
/**
* ftl文件名
*
* @param filePath
*/
public static String templateFilePath(String filePath) {
return filePath + com.seerbigdata.common.utils.support.FileToMultipartFileUtils.buildFileSuffix(FileTypeConstants.FTL);
}
}
FileToMultipartFileUtils
package com.seerbigdata.common.utils.support;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.*;
/**
* 转换工具类
*
* @author : zhangzhiqin / zhangzhiqin@seerbigdata.com
* @since 2021-08-26
* @version 1.0
*/
@UtilityClass
@Slf4j
public class FileToMultipartFileUtils {
/**
* file to multipartFile
*
* @param file
* @param fileItem
* @return
* @throws IOException
*/
public MultipartFile fileToMultipartFile(File file, FileItem fileItem) throws IOException {
try (InputStream is = new FileInputStream(file); OutputStream os = fileItem.getOutputStream()) {
IoUtil.copy(is, os);
} catch (IOException e) {
log.error("file转multipartFile发生错误:{}", e.getMessage());
throw e;
}
return new CommonsMultipartFile(fileItem);
}
/**
* 构建文件后缀名 ".html"、".pdf"...
*
* @param fileType 文件类型枚举
*/
public static String buildFileSuffix(String fileType) {
return StringPool.DOT + fileType;
}
}
4.准备拼接
package com.seerbigdata.ccisp.inspect.strategy;
import cn.hutool.core.date.DatePattern;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.google.common.base.Joiner;
import com.seerbigdata.ccisp.base.service.IEnterpriseService;
import com.seerbigdata.ccisp.core.constant.FoodstuffInspectResultFieldNameConstant;
import com.seerbigdata.ccisp.core.constant.inspection.InspectItemLevel;
import com.seerbigdata.ccisp.core.constant.inspection.InspectSubjectResult;
import com.seerbigdata.ccisp.inspect.domain.entity.InspectOrder;
import com.seerbigdata.ccisp.inspect.domain.entity.InspectOrderItem;
import com.seerbigdata.ccisp.inspect.service.InspectOrderItemService;
import com.seerbigdata.ccisp.inspect.service.InspectOrderService;
import com.seerbigdata.ccisp.inspect.support.RiskPDFUtils;
import com.seerbigdata.sr.common.file.pojo.FileInfoDTO;
import com.seerbigdata.sr.common.file.toolkit.SrFileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* 月调度 - 风险监管类型生成PDF方式
*
* @version 1.0
* @Author yuanlinquan@seerbigdata.com
* @Date 2023/01/30/下午3:46
*/
@Component
@RequiredArgsConstructor
public class InspectResultStrategy implements InspectStrategy {
/**
* 智慧监管 食品经营监督检查结果记录 模板
*/
private final static String FOODSTUFF_INSPECT_RESULT_FTL = "template/result";
@Autowired
private InspectOrderService inspectOrderService;
@Autowired
private InspectOrderItemService inspectOrderItemService;
@Value("${ccisp.epidemic.wx-inspect-pdf-url}")
private String inspectPdfUrl;
@Override
public String chooseCheckForm(String inspectOrderId) {
Map<String, Object> dataMap = new HashMap<>(8);
//根据id生成
InspectOrder inspectOrder = inspectOrderService.getById(inspectOrderId);
Assert.notNull(inspectOrder, "当前检查记录不存在, 请稍后重试");
List<InspectOrderItem> orderItems = inspectOrderItemService
.lambdaQuery()
.eq(InspectOrderItem::getInspectOrderId, inspectOrder.getId())
.list();
// 填充数据集合
populateData(dataMap, inspectOrder, orderItems);
// 获取生成的PDF文件编号
return RiskPDFUtils.generateEformWithFtlTemplate(FOODSTUFF_INSPECT_RESULT_FTL, dataMap, inspectOrderId,
buildPdfName(LocalDateTime.now()), Boolean.TRUE,null);
}
/**
* 填充数据
*
* @param dataMap 数据集合Map
* @param inspectOrder 智慧监管-食品经营监督检查结果记录
*/
private void populateData(Map<String, Object> dataMap, InspectOrder inspectOrder, List<InspectOrderItem> orderItems) {
List<String> subjectNames1 = orderItems.stream().filter(e -> e.getParentTitle().equals("食品通用检查项目")).map(item->{
return String.valueOf(item.getSubjectNumber()).concat(".").concat(item.getSubjectName());
}).distinct().collect(Collectors.toList());
List<String> subjectNames2 = orderItems.stream().filter(e -> e.getParentTitle().equals("食品其他检查项目")).map(item->{
return String.valueOf(item.getSubjectNumber()).concat(".").concat(item.getSubjectName());
}).distinct().collect(Collectors.toList());
List<String> subjectNames3 = orderItems.stream().filter(e -> e.getParentTitle().equals("相关主体检查项目")).map(item->{
return String.valueOf(item.getSubjectNumber()).concat(".").concat(item.getSubjectName());
}).distinct().collect(Collectors.toList());
Set<String> parentTitles = orderItems.stream().map(InspectOrderItem::getParentTitle).collect(Collectors.toSet());
//2.要点记录信息
dataMap.put(FoodstuffInspectResultFieldNameConstant.SUBJECT_NAMES1, subjectNames1);
dataMap.put(FoodstuffInspectResultFieldNameConstant.SUBJECT_NAMES2, subjectNames2);
dataMap.put(FoodstuffInspectResultFieldNameConstant.SUBJECT_NAMES3, subjectNames3);
dataMap.put(FoodstuffInspectResultFieldNameConstant.ITMES, orderItems);
dataMap.put(FoodstuffInspectResultFieldNameConstant.PARENT_TITLES, parentTitles);
// 1. 填充记录编号
dataMap.put(FoodstuffInspectResultFieldNameConstant.RECORD_ID, inspectOrder.getId());
//2.被检查单位名称.
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_NAME, inspectOrder.getTargetName());
//3.经营地址
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_ADDERSS, inspectOrder.getTargetAdderss());
//4.联系人
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_CONTACTS, inspectOrder.getTargetAccompanyPerson());
//5.联系方式
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_CONTACTS_TEL, inspectOrder.getTargetContactsTel());
//6.许可证编号或备案编号
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_LEGAL_ENTITY_USC_CODE, inspectOrder.getTargetCertNo());
//7.检查次数
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_INSPECT_NUMBER, inspectOrder.getTargetInspectNumber());
//8.检查方(市场监督管理部门全称)
dataMap.put(FoodstuffInspectResultFieldNameConstant.EXECUTOR_TENANT_NAME, inspectOrder.getExecutorTenantName());
//8.1检查人员
dataMap.put(FoodstuffInspectResultFieldNameConstant.EXECUTOR_NAME,inspectOrder.getExecutorName());
//9.开始时间 年
dataMap.put(FoodstuffInspectResultFieldNameConstant.BEGIN_TIME_YEAR, String.valueOf(inspectOrder.getBeginTime().getYear()));
//9.1.开始时间 月
dataMap.put(FoodstuffInspectResultFieldNameConstant.BEGIN_TIME_MONTH, inspectOrder.getBeginTime().getMonthValue());
//9.2.开始时间 日
dataMap.put(FoodstuffInspectResultFieldNameConstant.BEGIN_TIME_DAY, inspectOrder.getBeginTime().getDayOfMonth());
//10.1.结束时间 年
dataMap.put(FoodstuffInspectResultFieldNameConstant.END_TIME_YEAR, String.valueOf(inspectOrder.getEndTime().getYear()));
//10.2.结束时间 月
dataMap.put(FoodstuffInspectResultFieldNameConstant.END_TIME_MONTH, inspectOrder.getEndTime().getMonthValue());
//10.3.结束时间 日
dataMap.put(FoodstuffInspectResultFieldNameConstant.END_TIME_DAY, inspectOrder.getEndTime().getDayOfMonth());
//10.检查类型
dataMap.put(FoodstuffInspectResultFieldNameConstant.ORDER_TYPE_NAME, inspectOrder.getOrderTypeName());
//11.检查要点类型
// dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_ORDER_TYPE_NAME, inspectOrder.getInspectOrderTypeName());
//12.检查总项
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_ITEM_COUNT, inspectOrder.getInspectItemCount());
//13.检查重点项
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_POINT_ITEM_COUNT, inspectOrder.getInspectPointItemCount());
//14.检查一般项
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_GENERAL_ITEM_COUNT, inspectOrder.getInspectGeneralItemCount());
//15.检查不符合总项
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_ITEM_DISQUALIFICATION_COUNT, inspectOrder.getInspectItemDisqualificationCount());
//16.检查不符合重点项
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_POINT_ITEM_UNQUALIFIED_COUNT, inspectOrder.getInspectPointItemUnqualifiedCount());
//17.检查不符合重点项内容
List<String> point = orderItems.stream().
filter(e -> e.getItemLevel().getCode().equals(InspectItemLevel.POINT.getCode()) && InspectSubjectResult.UNQUALIFIED.getCode().equals(e.getResult().getCode()))
.map(item -> {
if (StringUtils.isEmpty(item.getSubjectNumber())){
return item.getItemContent();
}
return String.valueOf(item.getSubjectNumber()).concat("-").concat(String.valueOf(item.getNumber())).concat(item.getItemContent());
})
.collect(Collectors.toList());
dataMap.put(FoodstuffInspectResultFieldNameConstant.POINT, point);
//18.检查不符合一般项
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_GENERAL_ITEM_UNQUALIFIED_COUNT, inspectOrder.getInspectGeneralItemUnqualifiedCount());
//19.检查不符合一般项内容 InspectItemLevel
List<String> general = orderItems.stream()
.filter(e -> e.getItemLevel().getCode().equals(InspectItemLevel.GENERAL.getCode()) && InspectSubjectResult.UNQUALIFIED.getCode().equals(e.getResult().getCode()))
.map(item->{
if (StringUtils.isEmpty(item.getSubjectNumber())){
return item.getItemContent();
}
return String.valueOf(item.getSubjectNumber()).concat("-").concat(String.valueOf(item.getNumber())).concat(item.getItemContent());
}).collect(Collectors.toList());
dataMap.put(FoodstuffInspectResultFieldNameConstant.GENERAL, general);
//20.结果处理
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_RESULT_HANDLE, inspectOrder.getInspectResultHandle());
//21.说明
//22.检查人员(签名)
dataMap.put(FoodstuffInspectResultFieldNameConstant.INSPECT_SIGN_URL, inspectOrder.getInspectSignId());
//23.检查执法证件编号
dataMap.put(FoodstuffInspectResultFieldNameConstant.CERTIFICATE_NUMBER, inspectOrder.getLawEnforcementCertificateNumber());
//24.1.时间 年
dataMap.put(FoodstuffInspectResultFieldNameConstant.BEGIN_TIME_YEAR, String.valueOf(inspectOrder.getBeginTime().getYear()));
//24.2.时间 月
dataMap.put(FoodstuffInspectResultFieldNameConstant.BEGIN_TIME_MONTH, inspectOrder.getBeginTime().getMonthValue());
//24.2.时间 日
dataMap.put(FoodstuffInspectResultFieldNameConstant.BEGIN_TIME_DAY, inspectOrder.getBeginTime().getDayOfMonth());
//25.被检查单位意见
dataMap.put(FoodstuffInspectResultFieldNameConstant.TARGET_OPINION, inspectOrder.getTargetOpinion());
//26.法定代表人或负责人
dataMap.put(FoodstuffInspectResultFieldNameConstant.BE_INSPECT_SIGN_URL, inspectOrder.getBeInspectSignId());
//27.1.时间 年
dataMap.put(FoodstuffInspectResultFieldNameConstant.END_TIME_YEAR, String.valueOf(inspectOrder.getBeginTime().getYear()));
//27.1.时间 月
dataMap.put(FoodstuffInspectResultFieldNameConstant.END_TIME_MONTH, inspectOrder.getEndTime().getMonthValue());
//27.1.时间 日
dataMap.put(FoodstuffInspectResultFieldNameConstant.END_TIME_DAY, inspectOrder.getEndTime().getDayOfMonth());
//28. 文件前缀
dataMap.put(FoodstuffInspectResultFieldNameConstant.DOWNLOAD_URL_PREFIX, inspectPdfUrl);
}
/**
* 生成PDF文件名称
*
* @param createTime 发生日期
* @return PDF文件名称
*/
private String buildPdfName(LocalDateTime createTime) {
// 检查类型
return Optional.ofNullable(createTime).map(date -> date.format(DateTimeFormatter.ofPattern(DatePattern.CHINESE_DATE_PATTERN))).orElse(StringPool.EMPTY)
.concat(String.format("冷库冷链%s检查结果记录", "检查结果记录表"));
}
}