4

【玩转PDF】贼稳,产品要做一个三方合同签署,我方了!

 3 years ago
source link: https://www.cnblogs.com/mingyueyy/p/14821398.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【玩转PDF】贼稳,产品要做一个三方合同签署,我方了!

事情是这个样子的,小农的公司,之前有个功能需要签署来进行一系列的操作,于是我们引入了一个三方平台的签署——上上签,但是有一个比较尴尬的点就是,它不支持合同在浏览器上和附件一起预览的,我们想要的是需要将附件拼接在合同主文件中一起展示,但是它不支持,于是我们就开了一个需求会。。。

产品说,我们要做一个线上合同签署的功能,不依靠第三方来完成,可以浏览器上预览和下载合同,小农,你这边能做吗?

我一听,这个啊,这个有点难度啊(我需要时间),不太好做,之前我们接入的第三方就没有完全完成浏览器预览的功能,相当于我们做一个和这个第三方一模一样的东西,而且还要比它那个兼容更多的功能,不太好做(确实有点不太好做),加上之前也没有做过,心里没有底。

产品说,这个没有办法(你做也得做,不做也得做),是领导要求的(上面要求的,你只能做),你看下完成这些功能大概需要多久?

于是只能硬着头皮上了,于是给了一个大概的时间后,就开始研究了,什么是快乐星球,如果你想知道的话,那我就带你研究,what???等等,跑偏了,回来回来。
在这里插入图片描述

二、那我就带你研究

研究什么?什么是快乐星球[手动狗头],咳咳,洗脑了,请你立即停止你的傻*行为。

我们知道,如果是想要操作PDF的话(因为签署合同一般都是用的PDF,同志们为你们解疑了,掌声可以响起来了),所以一般都是用iText(PDF 操作类库)操作类库??? ,咳咳,你怎么回事?
在这里插入图片描述

我们一般都是要使用 Adobe工具设计表单和iText 来进行内容操作,这也是我们今天需要讲解的主体,知道了用什么,我们来讲一下我们的需求是什么?工作后的小伙伴有没有觉得很可怕,“我们来讲一下需求”,首先需要实现的是 通过PDF模板填充我们对应的甲乙方基本数据后,生成PDF文件,然后再将数据库的特定数据拼接在PDF里面一起,通过甲乙方先后签署后,然后让该合同进行生效操作!可以预览和下载。

要求就是这么个要求,听着倒是不难,主要是之前没有做过,心里不太有谱,但是做完之后,不得不呼自己真是个天才啊,我可真聪明,想起小农从实习的时候就是做的PDF,如今工作这么久了还是在做PDF的,真是漂(cao)亮(dan),那就做呗,谁怕谁啊!

三、Adobe工具

工欲善其事必先利其器,首先如果想要填充PDF模板里面的内容的话,我们需要使用到Adobe Acrobat Peo DC这个工具

下载地址:

链接:https://pan.baidu.com/s/1JdeKr7-abc4bajhVxoiYWg
提取码:6h0i

1、打开PDF文件

当我们下载好Adobe Acrobat Peo DC后,用它打开PDF文件,然后点击 准备表单
在这里插入图片描述

2、添加文本域

点击添加文本域
在这里插入图片描述

3、填写变量名

这个变量名就是我们填充数据的参数,要一一对应
在这里插入图片描述

4、填写完成后,如下所示

在这里插入图片描述

3、开始安排

别担心小伙伴们,项目都给你们准备好了
项目地址:https://github.com/muxiaonong/other/tree/master/pdf_sign_demo

jar文件:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.5</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>layout</artifactId>
            <version>7.1.15</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>

        <!--itext生成word文档,需要下面dependency-->
        <dependency>
            <groupId>com.lowagie</groupId>
            <artifactId>iText-rtf</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

根据PDF模板生成文件和拼接产品的数据

    /**
     * 根据PDF模板生成PDF文件
     * @return
     */
    @GetMapping("generatePdf")
    public String generatePdf() throws Exception{
//        File file = ResourceUtils.getFile("classpath:"+SAVE_PATH);
        File pdfFile = new File(ResourceUtils.getURL("classpath:").getPath()+SAVE_PATH);
        try {
            PdfReader pdfReader;
            PdfStamper pdfStamper;
            ByteArrayOutputStream baos;

            Document document = new Document();
//

            PdfSmartCopy pdfSmartCopy = new PdfSmartCopy(document,
                    new FileOutputStream(pdfFile));

            document.open();

            File file = ResourceUtils.getFile("classpath:"+templatePath);
            pdfReader = new PdfReader(file.getPath());
            int n = pdfReader.getNumberOfPages();
            log.info("页数:"+n);
            baos = new ByteArrayOutputStream();
            pdfStamper = new PdfStamper(pdfReader, baos);

            for(int i = 1; i <= n; i++) {
                AcroFields acroFields = pdfStamper.getAcroFields();

                //key statement 1
                acroFields.setGenerateAppearances(true);

                //acroFields.setExtraMargin(5, 5);
                acroFields.setField("customerAddress", "上海市浦东新区田子路520弄1号楼");
                acroFields.setField("customerCompanyName", "上海百度有限公司");
                acroFields.setField("customerName", "张三");
                acroFields.setField("customerPhone", "15216667777");
                acroFields.setField("customerMail", "[email protected]");

                acroFields.setField("vendorAddress", "上海市浦东新区瑟瑟发抖路182号");
                acroFields.setField("vendorCompanyName", "牧小农科技技术有限公司");
                acroFields.setField("vendorName", "王五");
                acroFields.setField("vendorPhone", "15688886666");
                acroFields.setField("vendorMail", "[email protected]");

                acroFields.setField("effectiveStartTime", "2021年05月25");
                acroFields.setField("effectiveEndTime", "2022年05月25");

                //true代表生成的PDF文件不可编辑
                pdfStamper.setFormFlattening(true);

                pdfStamper.close();

                pdfReader = new PdfReader(baos.toByteArray());


                pdfSmartCopy.addPage(pdfSmartCopy.getImportedPage(pdfReader, i));
                pdfSmartCopy.freeReader(pdfReader);
                pdfReader.close();
            }
            pdfReader.close();
            document.close();
        } catch(DocumentException dex) {
            dex.printStackTrace();
        } catch(IOException ex) {
            ex.printStackTrace();
        }
        //创建PDF文件
        createPdf();


        File file3 = new File(ResourceUtils.getURL("classpath:").getPath()+TEMP_PATH);
        File file1 = new File(ResourceUtils.getURL("classpath:").getPath()+outputFileName);

        List<File> files = new ArrayList<>();
        files.add(pdfFile);
        files.add(file3);

        try {
            PdfUtil pdfUtil = new PdfUtil();
            pdfUtil.mergeFileToPDF(files,file1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //如果你是上传文件服务器上,这里可以上传文件
//        String url = fileServer.uploadPdf(File2byte(file1));

        //删除总文件
        //如果是你本地预览就不要删除了,删了就看不到了
//        if(file1.exists()){
//            file1.delete();
//        }
        //删除模板文件
        if(pdfFile.exists()){
            System.gc();
            pdfFile.delete();
        }
        //删除产品文件
        if(file3.exists()){
            file3.delete();
        }
        return "success";
    }

创建PDF附件信息拼接到主文件中:

/**
     * 创建PDF附件信息
     */
    public static void createPdf() {
        Document doc = null;
        try {
            doc = new Document();
            PdfWriter.getInstance(doc, new FileOutputStream(ResourceUtils.getURL("classpath:").getPath()+TEMP_PATH));
            doc.open();
            BaseFont bfChi = BaseFont.createFont("STSong-Light","UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            Font fontChi = new Font(bfChi, 8, Font.NORMAL);

            PdfPTable table = new PdfPTable(5);

            Font fontTitle = new Font(bfChi, 15, Font.NORMAL);
            PdfPCell cell = new PdfPCell(new Paragraph("*货运*运输服务协议-附件1 运输费用报价",fontTitle));


            cell.setColspan(5);
            table.addCell(cell);
//			"序号"
            table.addCell(new Paragraph("序号",fontChi));
            table.addCell(new Paragraph("品类",fontChi));
            table.addCell(new Paragraph("名称",fontChi));
            table.addCell(new Paragraph("计算方式",fontChi));
            table.addCell(new Paragraph("费率",fontChi));

            table.addCell(new Paragraph("1",fontChi));
            table.addCell(new Paragraph("货运",fontChi));
            table.addCell(new Paragraph("费率1.0",fontChi));
            table.addCell(new Paragraph("算",fontChi));
            table.addCell(new Paragraph("0~100万-5.7%,上限:500元,下限:20元",fontChi));

            table.addCell(new Paragraph("2",fontChi));
            table.addCell(new Paragraph("货运",fontChi));
            table.addCell(new Paragraph("费率1.0",fontChi));
            table.addCell(new Paragraph("倒",fontChi));
            table.addCell(new Paragraph("100万~200万-5.6%,无上限、下限",fontChi));

            table.addCell(new Paragraph("3",fontChi));
            table.addCell(new Paragraph("货运",fontChi));
            table.addCell(new Paragraph("费率1.0",fontChi));
            table.addCell(new Paragraph("算",fontChi));
            table.addCell(new Paragraph("200万~300万-5.5%,无上限、下限",fontChi));


            doc.add(table);

//			doc.add(new Paragraph("Hello World,看看中文支持不........aaaaaaaaaaaaaaaaa",fontChi));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            doc.close();
        }
    }

合同签署:

/**
     * 签署合同
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    @GetMapping("addContent")
    public String addContent() throws IOException, DocumentException {

        BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font font = new Font(baseFont);

        //这里可以填写本地地址,也可以是服务器上的文件地址
        PdfReader reader = new PdfReader(ResourceUtils.getURL("classpath:").getPath()+outputFileName);
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(ResourceUtils.getURL("classpath:").getPath()+endPdf));

//
        PdfContentByte over = stamper.getOverContent(1);
        ColumnText columnText = new ColumnText(over);

        PdfContentByte over1 = stamper.getOverContent(1);
        ColumnText columnText1 = new ColumnText(over1);

        PdfContentByte over2 = stamper.getOverContent(1);
        ColumnText columnText2 = new ColumnText(over2);


        PdfContentByte over3 = stamper.getOverContent(1);
        ColumnText columnText3 = new ColumnText(over3);
        // llx 和 urx  最小的值决定离左边的距离. lly 和 ury 最大的值决定离下边的距离
        // llx 左对齐
        // lly 上对齐
        // urx 宽带
        // ury 高度
        columnText.setSimpleColumn(29, 117, 221, 16);
        Paragraph elements = new Paragraph(0, new Chunk("上海壹站供应链有限公司"));

        columnText1.setSimpleColumn(26, 75, 221, 16);
        Paragraph elements1 = new Paragraph(0, new Chunk("2021年03月03日"));

        columnText2.setSimpleColumn(800, 120, 200, 16);
        Paragraph elements2 = new Paragraph(0, new Chunk("壹汇(江苏)供应链管理有限公司芜湖分公司"));

        columnText3.setSimpleColumn(800, 74, 181, 16);
        Paragraph elements3 = new Paragraph(0, new Chunk("2022年03月03日"));

//            acroFields.setField("customerSigntime", "2021年03月03日");
//                acroFields.setField("vendorSigntime", "2021年03月09日");
        // 设置字体,如果不设置添加的中文将无法显示
        elements.setFont(font);
        columnText.addElement(elements);
        columnText.go();

        elements1.setFont(font);
        columnText1.addElement(elements1);
        columnText1.go();

        elements2.setFont(font);
        columnText2.addElement(elements2);
        columnText2.go();

        elements3.setFont(font);
        columnText3.addElement(elements3);
        columnText3.go();

        stamper.close();

        File tempFile = new File(ResourceUtils.getURL("classpath:").getPath()+"签署测试.pdf");

        //如果是你要上传到服务器上,填写服务器的地址
//        String url = fileServer.uploadPdf(File2byte(tempFile));
//        log.info("url:"+url);

        //如果是上传服务器后,要删除信息
        //本地不要删除,否则没有文件
//        if(tempFile.exists()){
//            tempFile.delete();
//        }

        return "success";
    }

PDF工具类:

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.List;

/***
 * pdf 相关操作
 * @author mxn
 */
@Slf4j
@Component
public class PdfUtil {

    /**
     * 合并PDF文件
     * @param files 文件列表
     * @param output 输出的PDF文件
     */
    public void mergeFileToPDF(List<File> files, File output) {
        Document document = null;
        PdfCopy copy = null;
        OutputStream os = null;
        try {
            os = new FileOutputStream(output);
            document = new Document();
            copy = new PdfCopy(document, os);
            document.open();
            for (File file : files) {
                if (!file.exists()) {
                    continue;
                }
                String fileName = file.getName();
                if (fileName.endsWith(".pdf")) {
                    PdfContentByte cb = copy.getDirectContent();
                    PdfOutline root = cb.getRootOutline();
                    new PdfOutline(root, new PdfDestination(PdfDestination.XYZ), fileName
                            .substring(0, fileName.lastIndexOf(".")));
                    // 不使用reader来维护文件,否则删除不掉文件,一直被占用
                    try (InputStream is = new FileInputStream(file)) {
                        PdfReader reader = new PdfReader(is);
                        int n = reader.getNumberOfPages();
                        for (int j = 1; j <= n; j++) {
                            document.newPage();
                            PdfImportedPage page = copy.getImportedPage(reader, j);
                            copy.addPage(page);
                        }
                    } catch(Exception e) {
                        log.warn("error to close file : {}" + file.getCanonicalPath(), e);
//                        e.printStackTrace();
                    }
                } else {
                    log.warn("file may not be merged to pdf. name:" + file.getCanonicalPath());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (document != null) {
                document.close();
            }
            if (copy != null) {
                copy.close();
            }
            if (os != null) {
                IOUtils.closeQuietly(os);
            }
        }
    }


    /**
     * 将文件转换成byte数组
     * @param file
     * @return
     * **/
    public static byte[] File2byte(File file){
        byte[] buffer = null;
        try {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            int n;
            while ((n = fis.read(b)) != -1) {
                bos.write(b, 0, n);
            }
            fis.close();
            bos.close();
            buffer = bos.toByteArray();
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
        return buffer;
    }
}

当我们编写完成之后,就来到了最关键的地方,测试了,心里还有点小激动,应该不会有BUG的

在这里插入图片描述

首先我们输入http://localhost:8080/generatePdf,生成填充模板,生成新的PDF文件并合并文件,生成完成之后我们会在项目的class目录下看到这个文件

在这里插入图片描述

打开我们的文件,就可以看到,对应的数据信息,到这里有惊无险,就查最后一步签署合同了
在这里插入图片描述

到这里如果能够把签署的信息,填写到合同签署的位置上,那我们就可以说大功告成了,我们输入签署的地址http://localhost:8080/addContent,当我们在目录下看到 签署测试.PDF的时候就说明我们大功告成了

在这里插入图片描述
在这里插入图片描述
我们可以看到对应的签署信息已经被我们添加上去了,除了没有第三方认证,该有的功能都有了,太优秀了啊!

这个时候我难免想起了,李白那句 “仰天大笑出门去,我辈岂是蓬蒿人”,天晴了,雨停了,我觉得我又行了,我都已经联想到产品小姐姐崇拜的小眼神了,魅力放光芒,请你不要再迷恋哥!别光喝酒啊,吃点菜啊,几个菜喝成这样
在这里插入图片描述

这个功能,花费了小农三天的时候,如果这个功能已经能够在公司项目中正常使用了,以上就是这个功能的详细代码和说明,如果有疑问或者也在做这个功能的小伙伴可以留言,小农看到了会第一时间回复

如果大家觉得不错,记得一键三连~

点赞过百,我就是怀双胞胎也出下一篇

我是牧小农,怕什么真理无穷,进一步有进一步的欢喜,大家加油!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK