当前位置: 代码网 > it编程>编程语言>Java > SpringBoot集成PDFBox实现电子签章的代码详解

SpringBoot集成PDFBox实现电子签章的代码详解

2024年09月08日 Java 我要评论
apache pdfbox 是一个开源的 java 库,用于处理 pdf 文档。它提供了一系列强大的功能,包括创建、渲染、拆分、合并、加密、解密 pdf 文件,以及从 pdf 中提取文本和元数据等。p

apache pdfbox 是一个开源的 java 库,用于处理 pdf 文档。它提供了一系列强大的功能,包括创建、渲染、拆分、合并、加密、解密 pdf 文件,以及从 pdf 中提取文本和元数据等。pdfbox 支持 pdf 1.7 标准,并且兼容大多数现代 pdf 格式和特性。

1、使用 maven 集成 pdfbox

在 pom.xml 文件中引入依赖

<dependency>
    <groupid>org.apache.pdfbox</groupid>
    <artifactid>pdfbox</artifactid>
    <version>2.0.24</version> <!-- 请检查最新的版本 -->
</dependency>

2、编写工具类

package cn.iocoder.yudao.module.contract.service.content;
 
import com.fasterxml.jackson.databind.objectmapper;
import org.apache.pdfbox.pdmodel.pddocument;
import org.apache.pdfbox.pdmodel.pdpage;
import org.apache.pdfbox.pdmodel.pdpagecontentstream;
import org.apache.pdfbox.pdmodel.common.pdrectangle;
import org.apache.pdfbox.pdmodel.graphics.image.pdimagexobject;
import org.apache.pdfbox.rendering.pdfrenderer;
import org.springframework.http.responseentity;
 
import javax.imageio.imageio;
import java.awt.image.bufferedimage;
import java.io.bytearrayinputstream;
import java.io.bytearrayoutputstream;
import java.io.ioexception;
import java.util.arraylist;
import java.util.base64;
import java.util.list;
 
public class pdfboxutil {
 
    /**
     * 加载 pdf 文档
     */
    public static pddocument loadpdf(byte[] input) throws ioexception {
        return pddocument.load(input);
    }
 
    /**
     * 添加印章到 pdf 文档中
     *
     * @param document       pdf 文档对象
     * @param imagebytearray 印章图像的二进制数据
     * @param x              横坐标
     * @param y              纵坐标
     * @param h              高度
     * @param pageidx        页码
     * @throws ioexception 异常
     */
    public static void addstamptopdf(pddocument document, byte[] imagebytearray, int x, int y, int h, int pageidx) throws ioexception {
        // 加载签章图像
        pdimagexobject pdimage = pdimagexobject.createfrombytearray(document, imagebytearray, "签章");
 
        // 获取 pdf 文档的第一个页面
        pdpage page = document.getpage(pageidx);
 
        // 计算签章图像的尺寸
        float desiredheight = h; // 目标高度
        float scale = desiredheight / pdimage.getheight();
 
        // 创建一个内容流以添加签章
        try (pdpagecontentstream contentstream = new pdpagecontentstream(document, page, pdpagecontentstream.appendmode.append, true, true)) {
            // 在 pdf 页面上绘制签章图像
            contentstream.drawimage(pdimage, x, y, pdimage.getwidth() * scale, pdimage.getheight() * scale);
        }
 
        // 可选:也可以向 pdf 添加一个签名字段
//        addsignaturefield(document);
    }
 
    /**
     * 将 bufferedimage 转换为字节数组
     *
     * @param image 要转换的图像
     * @return 字节数组
     */
    private static byte[] imagetobytes(bufferedimage image) {
        try (bytearrayoutputstream os = new bytearrayoutputstream()) {
            imageio.write(image, "png", os);
            return os.tobytearray();
        } catch (ioexception e) {
            throw new runtimeexception("failed to convert image to bytes", e);
        }
    }
 
    /**
     * 裁剪图像
     *
     * @param image 要裁剪的图像
     * @param page pdf 页面
     * @param x 开始裁剪的横坐标
     * @param y 开始裁剪的纵坐标
     * @param w 需要裁剪的宽度
     * @param h 需要裁剪的高度
     * @return 裁剪后的图片
     */
    private static bufferedimage cropimage(bufferedimage image, pdpage page, int x, int y, int w, int h) {
        pdrectangle mediabox = pdrectangle.a4; // 使用默认的 a4 大小
 
        // 将 pdf 单位转换为图像坐标
        int width = (int) (mediabox.getwidth() * (image.getwidth() / page.getmediabox().getwidth()));
        int height = (int) (mediabox.getheight() * (image.getheight() / page.getmediabox().getheight()));
 
        // 裁剪图像
        return image.getsubimage(x, y, width - w, height - h);
    }
    
    /**
     * 将 pdf 转换为多个图片
     *
     * @param pdfbytes pdf 二进制数据
     * @param dpi      dpi 值
     * @return 裁剪后的图片列表
     * @throws ioexception 异常
     */
    public static list<byte[]> convertpdftoimages(byte[] pdfbytes, int numberofpages, int dpi, int x, int y, int w, int h) throws ioexception {
        list<byte[]> croppedimages = new arraylist<>();
        try (pddocument document = pddocument.load(new bytearrayinputstream(pdfbytes))) {
            pdfrenderer renderer = new pdfrenderer(document);
            if (numberofpages == 0) {
                numberofpages = document.getnumberofpages();
            }
 
            for (int i = 0; i < numberofpages; i++) {
                // 渲染页面
                bufferedimage image = renderer.renderimagewithdpi(i, dpi); // 300 dpi
                // 裁剪图像
                bufferedimage croppedimage = cropimage(image, document.getpage(i), x, y, w, h);
                byte[] croppedimagebytes = imagetobytes(croppedimage);
                croppedimages.add(croppedimagebytes);
            }
        }
        return croppedimages;
    }
 
    /**
     * 将 pdf 转换为 base64 编码的 json
     *
     * @param filecontent pdf 二进制数据
     * @param x 开始裁剪的横坐标
     * @param y 开始裁剪的纵坐标
     * @param w 需要裁剪的宽度
     * @param h 需要裁剪的高度
     * @return base64 编码的 json
     * @throws exception 异常
     */
    public static responseentity<string> convertpdftobase64(byte[] filecontent, int x, int y, int w, int h) throws exception {
        list<byte[]> imagebyteslist = convertpdftoimages(filecontent, 0, 300, x, y, w, h);
 
        list<string> base64images = new arraylist<>();
        for (byte[] imagebytes : imagebyteslist) {
            string base64image = base64.getencoder().encodetostring(imagebytes);
            base64images.add(base64image);
        }
        objectmapper mapper = new objectmapper();
        string jsonresult = mapper.writevalueasstring(base64images);
        return responseentity.ok().body(jsonresult);
    }
}

3、编写控制器用于浏览器直接打开

第五步会编写控制器用于在 vue 前端预览 pdf 文件

/**
     * 测试添加数字签名
     *
     * @param filename 文件名
     * @param x x坐标
     * @param y y坐标
     * @param h 高度
     * @param i 宽度
     */
    @getmapping("/stamp/{filename}/p")
    @parameter(name = "x", description = "添加签名的 x 坐标", required = true, example = "x")
    @parameter(name = "y", description = "添加签名的 y 坐标", required = true, example = "y")
    @parameter(name = "h", description = "签名的显示高度", required = true, example = "h")
    @parameter(name = "i", description = "签名所在页数下标", required = true, example = "i")
    public responseentity<bytearrayresource> stamptest(@pathvariable string filename, @requestparam("x") integer x, @requestparam("y") integer y,
                                                    @requestparam("h") integer h, @requestparam("i") integer i) throws exception {
        // 从数据库中获取文件内容,这里需要修改为你们自己的获取方式来获取源 pdf 文件的字节数组
        byte[] filecontent = fileapi.getfilecontent(4l, filename);
        bytearrayoutputstream out = new bytearrayoutputstream();
 
        // 添加数字签名
        try (pddocument document = pdfboxutil.loadpdf(filecontent)) {
            // 这里需要修改为你们自己的获取方式来获取签名文件的字节数组
            byte[] imagebytearray = fileapi.getfilecontent(4l, "2c095928083c5ee82e6e229089892191d7790a3a42616dfd5a49daae68c27f41.png");
            pdfboxutil.addstamptopdf(document, imagebytearray, x, y, h, i);
            document.save(out);
        } catch (ioexception e) {
            e.printstacktrace();
        }
 
        // 创建 bytearrayresource
        bytearrayresource resource = new bytearrayresource(out.tobytearray());
 
        return responseentity.ok()
                .header(httpheaders.content_disposition, "inline; filename=\"" + filename + "\"")
                .contenttype(mediatype.application_pdf)
                .body(resource);
    }

4、浏览器测试

直接打开连接http://ip:端口/你们自己的控制器前缀/stamp/文件名/p?x=100&y=200&h=80&i=1进行测试

5、编写控制器用于在 vue 前端预览 pdf 文件

我这边在预览的时候不想保留边距、页眉、页脚的数据,所以有裁剪参数,不需要的话需要自行修改

/**
     * 根据合约名称获取合约 pdf 文件,并返回图片的 base64 编码
     *
     * @param filename合约标识
     * @return 图片的 base64 编码
     */
    @getmapping(value = "/get/{filename}", produces = mediatype.image_png_value)
    @parameter(name = "x", description = "每一页开始裁剪的 x 横坐标", required = true, example = "x")
    @parameter(name = "y", description = "每一页开始裁剪的 y 纵坐标", required = true, example = "y")
    @parameter(name = "h", description = "每一页需要裁剪掉的高度 h", required = true, example = "h")
    @parameter(name = "w", description = "每一个需要裁剪掉的宽度 w", required = true, example = "w")
    public responseentity<string> getpageimage(@pathvariable string filename, @requestparam("x") int x, @requestparam("y") int y,
                                                            @requestparam("h") int h, @requestparam("w") int w) {
        
        // 从数据库中获取文件内容,这里需要修改为你们自己的获取方式来获取源 pdf 文件的字节数组
        byte[] filecontent = fileapi.getfilecontent(4l, filename);
        
        try {
            return pdfboxutil.convertpdftobase64(filecontent, x, y, w, h);
        } catch (ioexception e) {
            throw new runtimeexception("获取 pdf 文件截图异常", e);
        } catch (exception e) {
            throw new runtimeexception("读取 pdf 文件异常", e);
        }
    }

6、编写 vue 代码

<template>
  <dialog :title="dialogtitle" v-model="dialogvisible">
    <div v-if="formloading">{{message}}</div>
    <div id="pdf-container">
    </div>
  </dialog>
</template>
<script setup lang="ts">
 
defineoptions({ name: 'contentwxpreview' })
 
const dialogvisible = ref(false) // 弹窗的是否展示
const dialogtitle = ref('') // 弹窗的标题
const formloading = ref(false) // 表单的加载中
const message = ref('数据正在加载请稍后 ... ...')
 
/** 打开弹窗 */
const open = async (title: string, code: string) => {
  dialogvisible.value = true
  dialogtitle.value = title + '_预览'
  formloading.value = true
  try {
    fetch('http://ip:端口/你们自己的控制器前缀/stamp/文件名/p?x=250&y=188&w=520&h=385', {
        method: 'get',
        headers: {
            'content-type': 'application/octet-stream'
        }
    })
    .then(response => response.text())
    .then(base64images => {
        const container = document.getelementbyid('pdf-container')
        if (container) {
          container.innerhtml = '' // 清空容器
 
          const images = json.parse(base64images)
          images.foreach(base64image => {
              let img = document.createelement('img')
              img.src = `data:image/png;base64,${base64image}`
              container.appendchild(img)
          })
        }
        formloading.value = false
    })
  } finally {
    formloading.value = false
  }
}
defineexpose({ open }) // 提供 open 方法,用于打开弹窗
</script>
 
<style lang="scss">
#pdf-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}
 
#pdf-container > img {
  max-width: 100%; 
}
</style>

7、预览显示

扩展:虽然 pdfbox 很强大,但是在读取文件、文件识别、文字替换等方面使用起来不是特别方便,需要有一定的学习成本。对于我这边偶尔开发 pdf 文档处理半路子来说太难了,所以会在springboot集成spirepdf实现文本替换功能_java_代码网 (jb51.net)说明如何使用 spider.pdf 进行文本替换

以上就是springboot集成pdfbox实现电子签章的代码详解的详细内容,更多关于springboot pdfbox电子签章的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com