java 制作签名版电子合同

Java 制作带签名的电子合同

根据项目需求,需要生成一个带电子签名的合同,即客户在手机进行签名后,将签名图片合成到合同中。

目前方案有两个,使用docx4j插件根据书签生成word版电子合同 或者 使用itext插件根据PDF模版生成PDF版电子合同。

方案一:使用docx4j生成word电子合同

本文参考文章来自:https://www.cnblogs.com/qlqwjy/p/9866484.html

1.引入docx4j

本案例使用maven仓库引入jar包

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j</artifactId>
    <version>6.1.2</version>
</dependency>

2.docx4j配置

可以不添加配置文件,但debug日志会提示找不到docx4j配置文件

在src下创建docx4j.properties配置文件,maven项目请放在resources文件夹下

# Page size: use a value from org.docx4j.model.structure.PageSizePaper enum
# eg A4, LETTER
docx4j.PageSize=LETTER
# Page size: use a value from org.docx4j.model.structure.MarginsWellKnown enum
docx4j.PageMargins=NORMAL
docx4j.PageOrientationLandscape=false
# Page size: use a value from org.pptx4j.model.SlideSizesWellKnown enum
# eg A4, LETTER
pptx4j.PageSize=LETTER
pptx4j.PageOrientationLandscape=false
# These will be injected into docProps/app.xml
# if App.Write=true
docx4j.App.write=true
docx4j.Application=docx4j
docx4j.AppVersion=6.1.2
# of the form XX.YYYY where X and Y represent numerical values
# These will be injected into docProps/core.xml
docx4j.dc.write=true
docx4j.dc.creator.value=docx4j
docx4j.dc.lastModifiedBy.value=docx4j
#
#docx4j.McPreprocessor=true
# If you haven't configured log4j yourself
# docx4j will autoconfigure it. Set this to true to disable that
docx4j.Log4j.Configurator.disabled=false

3.构建WordProcessingMlPackage对象

docx格式文档可以理解为一个压缩包,若将其解压可看到一个类似前端的工程项目,其中document.xml用于全文的配置,详细解说请自行查阅资料

WordprocessingMLPackage是操作word文档包对象;


创建新的word文档

import org.docx4j.openpackaging.packages.WordprocessingMLPackage;

/**
 * 构建新word文件
 */
public static WordprocessingMLPackage build() throws Exception{
    return WordprocessingMLPackage.createPackage();
}

加载存在的word文档

import org.docx4j.openpackaging.packages.WordprocessingMLPackage;

/**
 * 读取存在的word文件
 * @param wordFilePath word文件路径
 */
public static WordprocessingMLPackage load(String wordFilePath) throws Exception{
    return WordprocessingMLPackage.load(new File(wordFilePath));
}

4.获取书签

书签:CTBookmark对象

import org.docx4j.TraversalUtil;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.RangeFinder;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.org.apache.poi.util.IOUtils;
import org.docx4j.wml.*;

public static void main(String[] args) throws Exception {
    String wordFilePath = "";
    WordprocessingMLPackage wordMLPackage = load(wordFilePath);

    // 提取正文
    MainDocumentPart main = wordMLPackage.getMainDocumentPart();
    Document doc = main.getContents();
    Body body = doc.getBody();

    // 获取段落
    List<Object> paragraphs  = body.getContent();

    // 提取书签并获取书签的游标
    RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
    new TraversalUtil(paragraphs, rt);


    // 遍历书签
    for (CTBookmark bm : rt.getStarts()) {
        System.out.println("name:"+bm.getName());
    }
}

5.在书签位置加入图片

import org.docx4j.TraversalUtil;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.RangeFinder;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.org.apache.poi.util.IOUtils;
import org.docx4j.wml.*;

public static void addImage(WordprocessingMLPackage wPackage,CTBookmark bm) throws Exception{
    P p = (P) (bm.getParent());
    ObjectFactory factory = Context.getWmlObjectFactory();
    R run = factory.createR();

    // 读入图片并转化为字节数组,因为docx4j只能字节数组的方式插入图片
    // byte[] bytes = IOUtils.toByteArray(new FileInputStream("图片路径"));
    byte[] bytes = getFileBytes("图片路径");
    // 开始创建一个行内图片
    BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wPackage, bytes);
    // 最后一个是限制图片的宽度,缩放的依据
    Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 0);
    // 构建绘图
    Drawing drawing = factory.createDrawing();
    // 加入图片段落
    drawing.getAnchorOrInline().add(inline);
    run.getContent().add(drawing);
    // 清理书签所在数据
    // p.getContent().clear();
    // 加入图片信息
    p.getContent().add(run);

}

public static byte[] getFileBytes(String filePath) throws Exception{
    File file = new File(filePath);
    if(!file.exists()){
        throw new Exception("文件不存在!");
    }

    byte[] data = null;
    try(FileInputStream fis = new FileInputStream(file);ByteArrayOutputStream baos = new ByteArrayOutputStream()){
        int len;
        byte[] buffer = new byte[1024];
        while ((len = fis.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        data = baos.toByteArray();
    }
    return data;
}

6.保存文件

必须调用保存,否则更改无效

// 保存有两种方式
// 方式一
wordMLPackage.save(new File(wordFilePath));

// 方式二
Docx4J.save(wordMLPackage, new File(wordFilePath));

7.完整代码


import org.docx4j.TraversalUtil;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.RangeFinder;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.org.apache.poi.util.IOUtils;
import org.docx4j.wml.*;

import javax.xml.bind.JAXBElement;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WordTest {
    public static void main(String[] args) throws Exception {
        String wordFilePath = "word文档路径";
        WordprocessingMLPackage wordMLPackage = load(wordFilePath);

        // 提取正文
        MainDocumentPart main = wordMLPackage.getMainDocumentPart();
        Document doc = main.getContents();
        Body body = doc.getBody();

        // 获取段落
        List<Object> paragraphs  = body.getContent();

        // 提取书签并获取书签的游标
        RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
        new TraversalUtil(paragraphs, rt);

        // 遍历书签
        for (CTBookmark bm : rt.getStarts()) {
            System.out.println("name:"+bm.getName());
            // 替换image
            if ("sign".equals(bm.getName())){
                addImage(wordMLPackage, bm);
                break;
            }
        }

        save(wordMLPackage,wordFilePath);
    }

    public static void addImage(WordprocessingMLPackage wPackage,CTBookmark bm) throws Exception{
        P p = (P) (bm.getParent());
        ObjectFactory factory = Context.getWmlObjectFactory();
        R run = factory.createR();

        // 读入图片并转化为字节数组,因为docx4j只能字节数组的方式插入图片
        // byte[] bytes = IOUtils.toByteArray(new FileInputStream("图片文件路径"));
        byte[] bytes = getFileBytes("图片文件路径");
        // 开始创建一个行内图片
        BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wPackage, bytes);
        // 最后一个是限制图片的宽度,缩放的依据
        Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 0);
        // 构建绘图
        Drawing drawing = factory.createDrawing();
        // 加入图片段落
        drawing.getAnchorOrInline().add(inline);
        run.getContent().add(drawing);
        // 清理书签所在数据
        // p.getContent().clear();
        // 加入图片信息
        p.getContent().add(run);
    }

    /**
     * 构建文件
     */
    public static WordprocessingMLPackage build() throws Exception{
        return WordprocessingMLPackage.createPackage();
    }

    /**
     * 读取存在的word文件
     * @param wordFilePath word文件路径
     */
    public static WordprocessingMLPackage load(String wordFilePath) throws Exception{
        return WordprocessingMLPackage.load(new File(wordFilePath));
    }

    /**
     * 保存
     * @param wordMLPackage word
     */
    public static void save(WordprocessingMLPackage wordMLPackage,String wordFilePath) throws Exception{
        wordMLPackage.save(new File(wordFilePath));
    }

    public static byte[] getFileBytes(String filePath) throws Exception{
        File file = new File(filePath);
        if(!file.exists()){
            throw new Exception("文件不存在!");
        }

        byte[] data = null;
        try(FileInputStream fis = new FileInputStream(file);ByteArrayOutputStream baos = new ByteArrayOutputStream()){
            int len;
            byte[] buffer = new byte[1024];
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            data = baos.toByteArray();
        }
        return data;
    }
}

8.效果图

加入标签

使用docx4j在标签位置加入图片

方案二:使用itext生成PDF电子合同

1.引入itext

<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

2.设置表单数据&图片

import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.HashMap;

public class PDFTest {
    public static void main(String[] args) throws Exception {

        // 模版文件
        String templateFilePath = "模版文件路径";
        // 保存PDF文件
        String pdfFilePath = "保存PDF文件路径";
        // 表单数据
        HashMap<String,String> data = new HashMap<>();
        data.put("amount","100000");
        data.put("month","24");
        data.put("begin_date","2020年4月7日");
        data.put("end_date","2022年4月6日");
        // 图片数据
        HashMap<String,String> imageData = new HashMap<>();
        imageData.put("sign","签名图片");

        // 根据PDF模版生成PDF文件
        createPDF(templateFilePath,data,imageData,true,pdfFilePath);
    }

    /**
     * 根据PDF模版生成PDF文件
     * @param templateFilePath PDF模版文件路径
     * @param data 表单数据
     * @param imageData 图片数据 VALUE为图片文件路径
     * @param formFlattening false:生成后的PDF文件表单域仍然可编辑 true:生成后的PDF文件表单域不可编辑
     * @param pdfFilePath 生成PDF的文件路径
     */
    private static void createPDF(String templateFilePath, HashMap<String,String> data, HashMap<String,String> imageData,
                                  boolean formFlattening, String pdfFilePath) throws Exception{
        PdfReader reader = null;
        ByteArrayOutputStream bos = null;
        PdfStamper pdfStamper = null;
        FileOutputStream fos = null;
        try{
            // 读取PDF模版文件
            reader = new PdfReader(templateFilePath);
            // 输出流
            bos = new ByteArrayOutputStream();
            // 构建PDF对象
            pdfStamper = new PdfStamper(reader, bos);

            // 获取表单数据
            AcroFields form = pdfStamper.getAcroFields();

            // 使用中文字体 使用 AcroFields填充值的不需要在程序中设置字体,在模板文件中设置字体为中文字体 Adobe 宋体 std L
            BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            form.addSubstitutionFont(bfChinese);

            // 表单赋值
            for(String key : data.keySet()){
                form.setField(key,data.get(key));
                // 也可以指定字体
                form.setFieldProperty(key, "textfont", bfChinese, null);
            }

            // 添加图片
            if(null != imageData && imageData.size() > 0){
                for(String key : imageData.keySet()){
                    int pageNo = form.getFieldPositions(key).get(0).page;
                    Rectangle signRect = form.getFieldPositions(key).get(0).position;
                    float x = signRect.getLeft();
                    float y = signRect.getBottom();
                    // 读图片
                    Image image = Image.getInstance(imageData.get(key));
                    // 获取操作的页面
                    PdfContentByte under = pdfStamper.getOverContent(pageNo);
                    // 根据域的大小缩放图片
                    image.scaleToFit(signRect.getWidth(), signRect.getHeight());
                    // 添加图片
                    image.setAbsolutePosition(x, y);
                    under.addImage(image);
                }
            }

            // 如果为false那么生成的PDF文件还能编辑,一定要设为true
            pdfStamper.setFormFlattening(formFlattening);
            pdfStamper.close();

            // 保存文件
            fos = new FileOutputStream(pdfFilePath);
            fos.write(bos.toByteArray());
            fos.flush();
        }finally {
            if(null != fos){
                try {fos.close(); }catch (Exception e){e.printStackTrace();}
            }

            if(null != bos){
                try {bos.close(); }catch (Exception e){e.printStackTrace();}
            }

            if(null != reader){
                try {reader.close(); }catch (Exception e){e.printStackTrace();}
            }
        }
    }
}

3.签名效果

PDF模版文件

生成PDF文件

总结

itext比docx使用方式更友好,通过PDF模版可以设置表单字体以及样式;

docx好处在于不需要依赖第三方PDF编辑器,只需要使用word自带的书签功能,辅助程序定位

两者原理均一样,需要约定KEY名称,便于程序定位填充数据

异常定位

itext报:javax.xml.parsers.DocumentBuilderFactory.setFeature(Ljava/lang/String;Z)V

这个错误由引入了jaxen、xom、xstream引起的xml工厂多个实现导致,删除引入即可

 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值