如何用Java实现合同模板签署的功能,做完产品都说好

开发 前端
我们一般都是要使用 Adobe工具设计表单和iText 来进行内容操作,这也是我们今天需要讲解的主体,知道了用什么,我们来讲一下我们的需求是什么?

[[403629]]

一、前言

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

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

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

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

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

二、那我就带你研究

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

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

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

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

三、Adobe工具

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

下载地址

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

1、打开PDF文件

当我们下载好 AdobeAcrobatPeoDC后,用它打开PDF文件,然后点击 准备表单

2、添加文本域

点击添加文本域

3、填写变量名

这个变量名就是我们填充数据的参数,要一一对应

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

5、开始安排

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

jar文件:

  1. <dependencies> 
  2.         <dependency> 
  3.             <groupId>org.springframework.boot</groupId> 
  4.             <artifactId>spring-boot-starter-web</artifactId> 
  5.         </dependency> 
  6.  
  7.         <dependency> 
  8.             <groupId>org.springframework.boot</groupId> 
  9.             <artifactId>spring-boot-starter-test</artifactId> 
  10.             <scope>test</scope> 
  11.         </dependency> 
  12.         <dependency> 
  13.             <groupId>com.itextpdf</groupId> 
  14.             <artifactId>itextpdf</artifactId> 
  15.             <version>5.5.5</version> 
  16.         </dependency> 
  17.         <dependency> 
  18.             <groupId>com.itextpdf</groupId> 
  19.             <artifactId>layout</artifactId> 
  20.             <version>7.1.15</version> 
  21.         </dependency> 
  22.  
  23.         <dependency> 
  24.             <groupId>com.itextpdf</groupId> 
  25.             <artifactId>itext-asian</artifactId> 
  26.             <version>5.2.0</version> 
  27.         </dependency> 
  28.         <dependency> 
  29.             <groupId>org.springframework</groupId> 
  30.             <artifactId>spring-test</artifactId> 
  31.         </dependency> 
  32.  
  33.         <!--itext生成word文档,需要下面dependency--> 
  34.         <dependency> 
  35.             <groupId>com.lowagie</groupId> 
  36.             <artifactId>iText-rtf</artifactId> 
  37.             <version>2.1.4</version> 
  38.         </dependency> 
  39.         <dependency> 
  40.             <groupId>commons-collections</groupId> 
  41.             <artifactId>commons-collections</artifactId> 
  42.             <version>3.2.2</version> 
  43.         </dependency> 
  44.         <dependency> 
  45.             <groupId>org.projectlombok</groupId> 
  46.             <artifactId>lombok</artifactId> 
  47.             <optional>true</optional> 
  48.         </dependency> 
  49.     </dependencies> 

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

  1.  /** 
  2.      * 根据PDF模板生成PDF文件 
  3.      * @return 
  4.      */ 
  5.     @GetMapping("generatePdf"
  6.     public String generatePdf() throws Exception{ 
  7. //        File file = ResourceUtils.getFile("classpath:"+SAVE_PATH); 
  8.         File pdfFile = new File(ResourceUtils.getURL("classpath:").getPath()+SAVE_PATH); 
  9.         try { 
  10.             PdfReader pdfReader; 
  11.             PdfStamper pdfStamper; 
  12.             ByteArrayOutputStream baos; 
  13.  
  14.             Document document = new Document(); 
  15. // 
  16.  
  17.             PdfSmartCopy pdfSmartCopy = new PdfSmartCopy(document, 
  18.                     new FileOutputStream(pdfFile)); 
  19.  
  20.             document.open(); 
  21.  
  22.             File file = ResourceUtils.getFile("classpath:"+templatePath); 
  23.             pdfReader = new PdfReader(file.getPath()); 
  24.             int n = pdfReader.getNumberOfPages(); 
  25.             log.info("页数:"+n); 
  26.             baos = new ByteArrayOutputStream(); 
  27.             pdfStamper = new PdfStamper(pdfReader, baos); 
  28.  
  29.             for(int i = 1; i <= n; i++) { 
  30.                 AcroFields acroFields = pdfStamper.getAcroFields(); 
  31.  
  32.                 //key statement 1 
  33.                 acroFields.setGenerateAppearances(true); 
  34.  
  35.                 //acroFields.setExtraMargin(5, 5); 
  36.                 acroFields.setField("customerAddress""上海市浦东新区田子路520弄1号楼"); 
  37.                 acroFields.setField("customerCompanyName""上海百度有限公司"); 
  38.                 acroFields.setField("customerName""张三"); 
  39.                 acroFields.setField("customerPhone""15216667777"); 
  40.                 acroFields.setField("customerMail""123456789@sian.com"); 
  41.  
  42.                 acroFields.setField("vendorAddress""上海市浦东新区瑟瑟发抖路182号"); 
  43.                 acroFields.setField("vendorCompanyName""牧小农科技技术有限公司"); 
  44.                 acroFields.setField("vendorName""王五"); 
  45.                 acroFields.setField("vendorPhone""15688886666"); 
  46.                 acroFields.setField("vendorMail""123567@qq.com"); 
  47.  
  48.                 acroFields.setField("effectiveStartTime""2021年05月25"); 
  49.                 acroFields.setField("effectiveEndTime""2022年05月25"); 
  50.  
  51.                 //true代表生成的PDF文件不可编辑 
  52.                 pdfStamper.setFormFlattening(true); 
  53.  
  54.                 pdfStamper.close(); 
  55.  
  56.                 pdfReader = new PdfReader(baos.toByteArray()); 
  57.  
  58.  
  59.                 pdfSmartCopy.addPage(pdfSmartCopy.getImportedPage(pdfReader, i)); 
  60.                 pdfSmartCopy.freeReader(pdfReader); 
  61.                 pdfReader.close(); 
  62.             } 
  63.             pdfReader.close(); 
  64.             document.close(); 
  65.         } catch(DocumentException dex) { 
  66.             dex.printStackTrace(); 
  67.         } catch(IOException ex) { 
  68.             ex.printStackTrace(); 
  69.         } 
  70.         //创建PDF文件 
  71.         createPdf(); 
  72.  
  73.  
  74.         File file3 = new File(ResourceUtils.getURL("classpath:").getPath()+TEMP_PATH); 
  75.         File file1 = new File(ResourceUtils.getURL("classpath:").getPath()+outputFileName); 
  76.  
  77.         List<File> files = new ArrayList<>(); 
  78.         files.add(pdfFile); 
  79.         files.add(file3); 
  80.  
  81.         try { 
  82.             PdfUtil pdfUtil = new PdfUtil(); 
  83.             pdfUtil.mergeFileToPDF(files,file1); 
  84.         } catch (Exception e) { 
  85.             e.printStackTrace(); 
  86.         } 
  87.  
  88.         //如果你是上传文件服务器上,这里可以上传文件 
  89. //        String url = fileServer.uploadPdf(File2byte(file1)); 
  90.  
  91.         //删除总文件 
  92.         //如果是你本地预览就不要删除了,删了就看不到了 
  93. //        if(file1.exists()){ 
  94. //            file1.delete(); 
  95. //        } 
  96.         //删除模板文件 
  97.         if(pdfFile.exists()){ 
  98.             System.gc(); 
  99.             pdfFile.delete(); 
  100.         } 
  101.         //删除产品文件 
  102.         if(file3.exists()){ 
  103.             file3.delete(); 
  104.         } 
  105.         return "success"
  106.     } 

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

  1. /** 
  2.      * 创建PDF附件信息 
  3.      */ 
  4.     public static void createPdf() { 
  5.         Document doc = null
  6.         try { 
  7.             doc = new Document(); 
  8.             PdfWriter.getInstance(doc, new FileOutputStream(ResourceUtils.getURL("classpath:").getPath()+TEMP_PATH)); 
  9.             doc.open(); 
  10.             BaseFont bfChi = BaseFont.createFont("STSong-Light","UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); 
  11.             Font fontChi = new Font(bfChi, 8, Font.NORMAL); 
  12.  
  13.             PdfPTable table = new PdfPTable(5); 
  14.  
  15.             Font fontTitle = new Font(bfChi, 15, Font.NORMAL); 
  16.             PdfPCell cell = new PdfPCell(new Paragraph("*货运*运输服务协议-附件1 运输费用报价",fontTitle)); 
  17.  
  18.  
  19.             cell.setColspan(5); 
  20.             table.addCell(cell); 
  21. //      "序号" 
  22.             table.addCell(new Paragraph("序号",fontChi)); 
  23.             table.addCell(new Paragraph("品类",fontChi)); 
  24.             table.addCell(new Paragraph("名称",fontChi)); 
  25.             table.addCell(new Paragraph("计算方式",fontChi)); 
  26.             table.addCell(new Paragraph("费率",fontChi)); 
  27.  
  28.             table.addCell(new Paragraph("1",fontChi)); 
  29.             table.addCell(new Paragraph("货运",fontChi)); 
  30.             table.addCell(new Paragraph("费率1.0",fontChi)); 
  31.             table.addCell(new Paragraph("算",fontChi)); 
  32.             table.addCell(new Paragraph("0~100万-5.7%,上限:500元,下限:20元",fontChi)); 
  33.  
  34.             table.addCell(new Paragraph("2",fontChi)); 
  35.             table.addCell(new Paragraph("货运",fontChi)); 
  36.             table.addCell(new Paragraph("费率1.0",fontChi)); 
  37.             table.addCell(new Paragraph("倒",fontChi)); 
  38.             table.addCell(new Paragraph("100万~200万-5.6%,无上限、下限",fontChi)); 
  39.  
  40.             table.addCell(new Paragraph("3",fontChi)); 
  41.             table.addCell(new Paragraph("货运",fontChi)); 
  42.             table.addCell(new Paragraph("费率1.0",fontChi)); 
  43.             table.addCell(new Paragraph("算",fontChi)); 
  44.             table.addCell(new Paragraph("200万~300万-5.5%,无上限、下限",fontChi)); 
  45.  
  46.  
  47.             doc.add(table); 
  48.  
  49. //      doc.add(new Paragraph("Hello World,看看中文支持不........aaaaaaaaaaaaaaaaa",fontChi)); 
  50.         } catch (FileNotFoundException e) { 
  51.             e.printStackTrace(); 
  52.         } catch (DocumentException e) { 
  53.             e.printStackTrace(); 
  54.         } catch (IOException e) { 
  55.             e.printStackTrace(); 
  56.         } finally { 
  57.             doc.close(); 
  58.         } 
  59.     } 

合同签署:

  1. /** 
  2.      * 签署合同 
  3.      * @return 
  4.      * @throws IOException 
  5.      * @throws DocumentException 
  6.      */ 
  7.     @GetMapping("addContent"
  8.     public String addContent() throws IOException, DocumentException { 
  9.  
  10.         BaseFont baseFont = BaseFont.createFont("STSong-Light""UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); 
  11.         Font font = new Font(baseFont); 
  12.  
  13.         //这里可以填写本地地址,也可以是服务器上的文件地址 
  14.         PdfReader reader = new PdfReader(ResourceUtils.getURL("classpath:").getPath()+outputFileName); 
  15.         PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(ResourceUtils.getURL("classpath:").getPath()+endPdf)); 
  16.  
  17. // 
  18.         PdfContentByte over = stamper.getOverContent(1); 
  19.         ColumnText columnText = new ColumnText(over); 
  20.  
  21.         PdfContentByte over1 = stamper.getOverContent(1); 
  22.         ColumnText columnText1 = new ColumnText(over1); 
  23.  
  24.         PdfContentByte over2 = stamper.getOverContent(1); 
  25.         ColumnText columnText2 = new ColumnText(over2); 
  26.  
  27.  
  28.         PdfContentByte over3 = stamper.getOverContent(1); 
  29.         ColumnText columnText3 = new ColumnText(over3); 
  30.         // llx 和 urx  最小的值决定离左边的距离. lly 和 ury 最大的值决定离下边的距离 
  31.         // llx 左对齐 
  32.         // lly 上对齐 
  33.         // urx 宽带 
  34.         // ury 高度 
  35.         columnText.setSimpleColumn(29, 117, 221, 16); 
  36.         Paragraph elements = new Paragraph(0, new Chunk("上海壹站供应链有限公司")); 
  37.  
  38.         columnText1.setSimpleColumn(26, 75, 221, 16); 
  39.         Paragraph elements1 = new Paragraph(0, new Chunk("2021年03月03日")); 
  40.  
  41.         columnText2.setSimpleColumn(800, 120, 200, 16); 
  42.         Paragraph elements2 = new Paragraph(0, new Chunk("壹汇(江苏)供应链管理有限公司芜湖分公司")); 
  43.  
  44.         columnText3.setSimpleColumn(800, 74, 181, 16); 
  45.         Paragraph elements3 = new Paragraph(0, new Chunk("2022年03月03日")); 
  46.  
  47. //            acroFields.setField("customerSigntime""2021年03月03日"); 
  48. //                acroFields.setField("vendorSigntime""2021年03月09日"); 
  49.         // 设置字体,如果不设置添加的中文将无法显示 
  50.         elements.setFont(font); 
  51.         columnText.addElement(elements); 
  52.         columnText.go(); 
  53.  
  54.         elements1.setFont(font); 
  55.         columnText1.addElement(elements1); 
  56.         columnText1.go(); 
  57.  
  58.         elements2.setFont(font); 
  59.         columnText2.addElement(elements2); 
  60.         columnText2.go(); 
  61.  
  62.         elements3.setFont(font); 
  63.         columnText3.addElement(elements3); 
  64.         columnText3.go(); 
  65.  
  66.         stamper.close(); 
  67.  
  68.         File tempFile = new File(ResourceUtils.getURL("classpath:").getPath()+"签署测试.pdf"); 
  69.  
  70.         //如果是你要上传到服务器上,填写服务器的地址 
  71. //        String url = fileServer.uploadPdf(File2byte(tempFile)); 
  72. //        log.info("url:"+url); 
  73.  
  74.         //如果是上传服务器后,要删除信息 
  75.         //本地不要删除,否则没有文件 
  76. //        if(tempFile.exists()){ 
  77. //            tempFile.delete(); 
  78. //        } 
  79.  
  80.         return "success"
  81.     } 

PDF工具类:

  1. import com.itextpdf.text.*; 
  2. import com.itextpdf.text.pdf.*; 
  3. import lombok.extern.slf4j.Slf4j; 
  4. import org.apache.tomcat.util.http.fileupload.IOUtils; 
  5. import org.springframework.stereotype.Component; 
  6.  
  7. import java.io.*; 
  8. import java.util.List; 
  9.  
  10. /*** 
  11.  * pdf 相关操作 
  12.  * @author mxn 
  13.  */ 
  14. @Slf4j 
  15. @Component 
  16. public class PdfUtil { 
  17.  
  18.     /** 
  19.      * 合并PDF文件 
  20.      * @param files 文件列表 
  21.      * @param output 输出的PDF文件 
  22.      */ 
  23.     public void mergeFileToPDF(List<File> files, File output) { 
  24.         Document document = null
  25.         PdfCopy copy = null
  26.         OutputStream os = null
  27.         try { 
  28.             os = new FileOutputStream(output); 
  29.             document = new Document(); 
  30.             copy = new PdfCopy(document, os); 
  31.             document.open(); 
  32.             for (File file : files) { 
  33.                 if (!file.exists()) { 
  34.                     continue
  35.                 } 
  36.                 String fileName = file.getName(); 
  37.                 if (fileName.endsWith(".pdf")) { 
  38.                     PdfContentByte cb = copy.getDirectContent(); 
  39.                     PdfOutline root = cb.getRootOutline(); 
  40.                     new PdfOutline(root, new PdfDestination(PdfDestination.XYZ), fileName 
  41.                             .substring(0, fileName.lastIndexOf("."))); 
  42.                     // 不使用reader来维护文件,否则删除不掉文件,一直被占用 
  43.                     try (InputStream is = new FileInputStream(file)) { 
  44.                         PdfReader reader = new PdfReader(is); 
  45.                         int n = reader.getNumberOfPages(); 
  46.                         for (int j = 1; j <= n; j++) { 
  47.                             document.newPage(); 
  48.                             PdfImportedPage page = copy.getImportedPage(reader, j); 
  49.                             copy.addPage(page); 
  50.                         } 
  51.                     } catch(Exception e) { 
  52.                         log.warn("error to close file : {}" + file.getCanonicalPath(), e); 
  53. //                        e.printStackTrace(); 
  54.                     } 
  55.                 } else { 
  56.                     log.warn("file may not be merged to pdf. name:" + file.getCanonicalPath()); 
  57.                 } 
  58.             } 
  59.         } catch (Exception e) { 
  60.             e.printStackTrace(); 
  61.         } finally { 
  62.             if (document != null) { 
  63.                 document.close(); 
  64.             } 
  65.             if (copy != null) { 
  66.                 copy.close(); 
  67.             } 
  68.             if (os != null) { 
  69.                 IOUtils.closeQuietly(os); 
  70.             } 
  71.         } 
  72.     } 
  73.  
  74.  
  75.     /** 
  76.      * 将文件转换成byte数组 
  77.      * @param file 
  78.      * @return 
  79.      * **/ 
  80.     public static byte[] File2byte(File file){ 
  81.         byte[] buffer = null
  82.         try { 
  83.             FileInputStream fis = new FileInputStream(file); 
  84.             ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
  85.             byte[] b = new byte[1024]; 
  86.             int n; 
  87.             while ((n = fis.read(b)) != -1) { 
  88.                 bos.write(b, 0, n); 
  89.             } 
  90.             fis.close(); 
  91.             bos.close(); 
  92.             buffer = bos.toByteArray(); 
  93.         }catch (FileNotFoundException e){ 
  94.             e.printStackTrace(); 
  95.         }catch (IOException e){ 
  96.             e.printStackTrace(); 
  97.         } 
  98.         return buffer; 
  99.     } 

演示

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

[[403630]]

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

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

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

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

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

总结

这个功能,花费了小农三天的时候,如果这个功能已经能够在公司项目中正常使用了,以上就是这个功能的详细代码和说明。

本文转载自微信公众号「 牧小农」,可以通过以下二维码关注。转载本文请联系 牧小农公众号。

 

责任编辑:姜华 来源: 牧小农
相关推荐

2011-07-22 13:22:10

Java.NETDataTable

2023-10-26 11:03:50

C语言宏定义

2013-04-11 09:56:07

2017-03-20 17:59:19

JavaScript模板引擎

2017-03-15 08:43:29

JavaScript模板引擎

2018-05-23 12:17:08

云计算签订合同数据

2024-03-25 09:00:00

人工智能深度学习对抗学习

2020-03-02 19:08:21

JVMJDKJRE

2022-03-23 08:01:04

Python语言代码

2018-02-24 16:32:06

云计算合同数据迁移

2010-08-30 09:01:29

DHCP功能宽带路由器

2018-02-08 16:45:22

前端JS粘贴板

2023-07-05 16:07:02

JavaScriptWeb 应用程序

2009-02-05 14:17:37

FTP服务器Java

2011-03-15 09:10:47

iptablesNAT

2011-03-15 14:26:23

iptablesNAT

2023-02-03 15:21:52

2022-02-09 07:31:27

产品功能护栏指标

2023-12-15 10:21:20

Java声音识别

2014-08-29 15:34:27

Web安全
点赞
收藏

51CTO技术栈公众号