一、前言

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

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

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

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

于是只能硬着头皮上了,于是给了一个大概的时间后,就开始研究了,什么是快乐星球,如果你想知道的话,那我就带你研究,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
解压密码:www.downza.cn

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 1acroFields.setGenerateAppearances(true);//acroFields.setExtraMargin(5, 5);acroFields.setField("customerAddress", "上海市浦东新区田子路520弄1号楼");acroFields.setField("customerCompanyName", "上海百度有限公司");acroFields.setField("customerName", "张三");acroFields.setField("customerPhone", "15216667777");acroFields.setField("customerMail", "123456789@sian.com");acroFields.setField("vendorAddress", "上海市浦东新区瑟瑟发抖路182号");acroFields.setField("vendorCompanyName", "牧小农科技技术有限公司");acroFields.setField("vendorName", "王五");acroFields.setField("vendorPhone", "15688886666");acroFields.setField("vendorMail", "123567@qq.com");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的时候就说明我们大功告成了



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

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

总结

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

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

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

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

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

  1. 如何做一个姿势正确的2B产品经理

    做一个产品经理,2B or not2B,是有很大差别的. 钱栩磊,资深产品经理,在凡客.京东.天猫电商大热期间,做过3年电商平台:又在2009年视频大战正式开始起步,腾讯视频.爱奇艺.乐视视频等视频平 ...

  2. 一个玩游戏的失足青年,转行做游戏开发到教育的挣扎过程(3)

    有一个人小时候就有当飞行员的愿望,终于有一天他当上了飞行员.但是他第一次飞行,飞机就出事了. 好在他能够跳伞,但是降落伞坏了,打不开,突然他看见了在正下方的地上有一个很高的干草垛,但是再往下掉他又发现 ...

  3. 徐姗玩赚中国:我用一个月时间做了一个几万粉的dy号,结果被限流了

    徐姗玩赚中国:我用一个月时间做了一个几万粉的dy号,结果被限流了 我在四川会理收突尼斯石榴的时候,在果地里刷到几个八块腹肌男孩子的视频(我本人比较喜欢看),就发现这些人虽然没有多少粉丝,但是点赞,评论 ...

  4. 一个玩游戏的失足青年,转行做软件开发的挣扎过程(1)

    14年的IT从业经历,中专毕业后在小镇上开过网吧.在网吧一年多的时间里,天天陪人玩游戏,后来去读了一个三流计算机专业,毕业后转做软件开发,最近五年转入游戏开发行业! 从网吧小老板到转行软件开发 1 开 ...

  5. python实用大全pdf_超级实用干货|九大技巧,带你用Python玩转PDF

    原标题:超级实用干货|九大技巧,带你用Python玩转PDF 尽管PDF最开始是由Adobe发明的,但它现在已经成为国际标准组织ISO维护的公开标准了.大家可以在Python中通过PyPDF2包来处理 ...

  6. 一个玩游戏的失足青年,转行做游戏开发到教育的挣扎过程

    14年的IT从业经历,中专毕业后在小镇上开过网吧.在网吧一年多的时间里,天天陪人玩游戏,后来去读了一个三流计算机专业,毕业后转做软件开发,最近五年转入游戏开发行业! 从网吧小老板到转行软件开发 1 开 ...

  7. 一个玩游戏的失足青年,转行做编程到教育的挣扎过程(2/4)

    本文接上一篇<一个玩游戏的失足青年,转行做编程到教育的挣扎过程(1/4)> 话说之前被父母迫切去相亲,前面两次都以失败告终,确实也是我没太放在心上. 现在回想起来,我想说的是,恋爱真的是一 ...

  8. 一个玩游戏的失足青年,转行做编程到教育的挣扎过程(3/4)

    有一个人,从小的心愿是当一名飞行员,终于有一天他当上了飞行员. 但是,他第一次飞行时,飞机就出事了. 好在他能够跳伞.但是,降落却是坏的,打不开...... 突然,他看见了在正下方的地上,有一个很高的 ...

  9. 一个玩游戏的失足青年,转行做编程到教育的挣扎过程(1/4)

    18年的IT从业经历,中专毕业后在小镇上开过网吧.在网吧一年多的时间里,天天陪人玩游戏,后来去读了一个三流计算机专业,毕业后转做软件开发,最近五年转入游戏开发行业! 从网吧小老板到转行软件开发 1 开 ...

  10. 周鸿祎:做一个产品彻底改变世界

    近来,作为管理营销类书籍,小米联合创始人黎万强的<参与感>火得不得了.就连曾经和小米有些"过节"的奇虎 360 董事长周鸿祎亦不吝溢美之词,一反常态推荐该书.其实除了& ...

最新文章

  1. Android Q 将获得大量的隐私保护功能
  2. SQL 2005单用户启动实例
  3. 桌面虚拟化最佳实践4—存储规划(下)
  4. BZOJ1566 [NOI2009]管道取珠
  5. python 计算过程图片_[Python图像处理]九.图像形态学相关运算
  6. Java当中TreeMap用法
  7. What is DevOps?
  8. opencv读写图片,分离通道等操作
  9. python广州地图_广东省客户数量地图展示,如何通过python实现?
  10. C51单片机实现 贪吃蛇
  11. double team
  12. 计算机科学大师唐纳德,现代计算机科学的鼻祖
  13. 北京元阔装饰安徽分公司 装修过后两年不到的墙面 及 相关
  14. vim 配置文件.vimrc[放在home目录下哦]
  15. 使用AFS, Active Directory和SSSD搭建用于集成电路设计的分布式存储系统 【十七】部署 AFS 客户端 2 统一身份登录
  16. 绝望爱の歌---最终兵器彼女
  17. EOJ 3344.送命题
  18. 《秋波媚·七月十六日晚登高兴亭望长安南山》 陆游
  19. 程序猿的口味-从C++STL聊到MSYS2-Qt
  20. python正则表达式——区间的表达方式

热门文章

  1. Inno Setup 制作安装包问题总结
  2. JVM 核心技术 22 讲
  3. eclipse导入html页面乱码,Eclipse导入项目乱码问题(中文乱码)
  4. 山东大学高频电子线路实验三 正弦波振荡器实验详解
  5. python调用大漠插件寻路_python调用大漠插件或天使插件
  6. 离线安装CDH5集群及相关软件
  7. 自主创新生态圈再扩大,深度科技与金格科技完成产品兼容性认证
  8. 自媒体内容采集工具,采集多个自媒体平台内容
  9. winform调用大华相机
  10. CmsEasy7.6.3.2逻辑漏洞