pdf导出

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&emsp;食品销售监督检查要点表</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&emsp;食品经营监督检查结果记录表</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检查结果记录", "检查结果记录表"));
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值