如何用Java實現(xiàn)合同模板簽署的功能,做完產(chǎn)品都說好
一、前言
事情是這個樣子的,小農(nóng)的公司,之前有個功能需要簽署來進(jìn)行一系列的操作,于是我們引入了一個三方平臺的簽署——上上簽,但是有一個比較尷尬的點就是,它不支持合同在瀏覽器上和附件一起預(yù)覽的,我們想要的是需要將附件拼接在合同主文件中一起展示,但是它不支持,于是我們就開了一個需求會。。。
- 產(chǎn)品說,我們要做一個線上合同簽署的功能,不依靠第三方來完成,可以瀏覽器上預(yù)覽和下載合同,小農(nóng),你這邊能做嗎?
我一聽,這個啊,這個有點難度啊(我需要時間),不太好做,之前我們接入的第三方就沒有完全完成瀏覽器預(yù)覽的功能,相當(dāng)于我們做一個和這個第三方一模一樣的東西,而且還要比它那個兼容更多的功能,不太好做(確實有點不太好做),加上之前也沒有做過,心里沒有底。
- 產(chǎn)品說,這個沒有辦法(你做也得做,不做也得做),是領(lǐng)導(dǎo)要求的(上面要求的,你只能做),你看下完成這些功能大概需要多久?
于是只能硬著頭皮上了,于是給了一個大概的時間后,就開始研究了,什么是快樂星球,如果你想知道的話,那我就帶你研究,what???等等,跑偏了,回來回來。
二、那我就帶你研究
研究什么?什么是快樂星球[手動狗頭],咳咳,洗腦了,請你立即停止你的傻*行為。
我們知道,如果是想要操作PDF的話(因為簽署合同一般都是用的PDF,同志們?yōu)槟銈兘庖闪?,掌聲可以響起來?,所以一般都是用 iText(PDF操作類庫),操作類庫??? ,咳咳,你怎么回事?
我們一般都是要使用 Adobe工具設(shè)計表單和iText 來進(jìn)行內(nèi)容操作,這也是我們今天需要講解的主體,知道了用什么,我們來講一下我們的需求是什么?工作后的小伙伴有沒有覺得很可怕,“我們來講一下需求”,首先需要實現(xiàn)的是 通過PDF模板填充我們對應(yīng)的甲乙方基本數(shù)據(jù)后,生成PDF文件,然后再將數(shù)據(jù)庫的特定數(shù)據(jù)拼接在PDF里面一起,通過甲乙方先后簽署后,然后讓該合同進(jìn)行生效操作!可以預(yù)覽和下載。
要求就是這么個要求,聽著倒是不難,主要是之前沒有做過,心里不太有譜,但是做完之后,不得不呼自己真是個天才啊,我可真聰明,想起小農(nóng)從實習(xí)的時候就是做的PDF,如今工作這么久了還是在做PDF的,真是漂(cao)亮(dan),那就做唄,誰怕誰啊!
三、Adobe工具
工欲善其事必先利其器,首先如果想要填充PDF模板里面的內(nèi)容的話,我們需要使用到 AdobeAcrobatPeoDC這個工具
下載地址:
鏈接:https://pan.baidu.com/s/1JdeKr7-abc4bajhVxoiYWg 提取碼:6h0i
1、打開PDF文件
當(dāng)我們下載好 AdobeAcrobatPeoDC后,用它打開PDF文件,然后點擊 準(zhǔn)備表單
2、添加文本域
點擊添加文本域
3、填寫變量名
這個變量名就是我們填充數(shù)據(jù)的參數(shù),要一一對應(yīng)
4、填寫完成后,如下所示
5、開始安排
別擔(dān)心小伙伴們,項目都給你們準(zhǔn)備好了項目地址:https://github.com/muxiaonong/other/tree/master/pdfsigndemo
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>
根據(jù)PDF模板生成文件和拼接產(chǎn)品的數(shù)據(jù)
- /**
- * 根據(jù)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("頁數(shù):"+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", "上海市浦東新區(qū)田子路520弄1號樓");
- acroFields.setField("customerCompanyName", "上海百度有限公司");
- acroFields.setField("customerName", "張三");
- acroFields.setField("customerPhone", "15216667777");
- acroFields.setField("customerMail", "123456789@sian.com");
- acroFields.setField("vendorAddress", "上海市浦東新區(qū)瑟瑟發(fā)抖路182號");
- acroFields.setField("vendorCompanyName", "牧小農(nóng)科技技術(shù)有限公司");
- 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();
- }
- //創(chuàng)建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();
- }
- //如果你是上傳文件服務(wù)器上,這里可以上傳文件
- // String url = fileServer.uploadPdf(File2byte(file1));
- //刪除總文件
- //如果是你本地預(yù)覽就不要刪除了,刪了就看不到了
- // if(file1.exists()){
- // file1.delete();
- // }
- //刪除模板文件
- if(pdfFile.exists()){
- System.gc();
- pdfFile.delete();
- }
- //刪除產(chǎn)品文件
- if(file3.exists()){
- file3.delete();
- }
- return "success";
- }
創(chuàng)建PDF附件信息拼接到主文件中:
- /**
- * 創(chuàng)建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("*貨運*運輸服務(wù)協(xié)議-附件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);
- //這里可以填寫本地地址,也可以是服務(wù)器上的文件地址
- 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("上海壹站供應(yīng)鏈有限公司"));
- 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("壹匯(江蘇)供應(yīng)鏈管理有限公司蕪湖分公司"));
- 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日");
- // 設(shè)置字體,如果不設(shè)置添加的中文將無法顯示
- 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");
- //如果是你要上傳到服務(wù)器上,填寫服務(wù)器的地址
- // String url = fileServer.uploadPdf(File2byte(tempFile));
- // log.info("url:"+url);
- //如果是上傳服務(wù)器后,要刪除信息
- //本地不要刪除,否則沒有文件
- // 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 相關(guān)操作
- * @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來維護(hù)文件,否則刪除不掉文件,一直被占用
- 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);
- }
- }
- }
- /**
- * 將文件轉(zhuǎn)換成byte數(shù)組
- * @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;
- }
- }
演示
當(dāng)我們編寫完成之后,就來到了最關(guān)鍵的地方,測試了,心里還有點小激動,應(yīng)該不會有BUG的
首先我們輸入 http://localhost:8080/generatePdf,生成填充模板,生成新的PDF文件并合并文件,生成完成之后我們會在項目的class目錄下看到這個文件
打開我們的文件,就可以看到,對應(yīng)的數(shù)據(jù)信息,到這里有驚無險,就查最后一步簽署合同了
到這里如果能夠把簽署的信息,填寫到合同簽署的位置上,那我們就可以說大功告成了,我們輸入簽署的地址 http://localhost:8080/addContent,當(dāng)我們在目錄下看到 簽署測試.PDF的時候就說明我們大功告成了
我們可以看到對應(yīng)的簽署信息已經(jīng)被我們添加上去了,除了沒有第三方認(rèn)證,該有的功能都有了,太優(yōu)秀了啊!
這個時候我難免想起了,李白那句 “仰天大笑出門去,我輩豈是蓬蒿人”,天晴了,雨停了,我覺得我又行了,我都已經(jīng)聯(lián)想到產(chǎn)品小姐姐崇拜的小眼神了,魅力放光芒,請你不要再迷戀哥!別光喝酒啊,吃點菜啊,幾個菜喝成這樣。
總結(jié)
這個功能,花費了小農(nóng)三天的時候,如果這個功能已經(jīng)能夠在公司項目中正常使用了,以上就是這個功能的詳細(xì)代碼和說明。
本文轉(zhuǎn)載自微信公眾號「 牧小農(nóng)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 牧小農(nóng)公眾號。