最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成PDFBox實(shí)現(xiàn)電子簽章的代碼詳解

 更新時(shí)間:2024年09月08日 09:54:47   作者:獨(dú)坐一隅  
Apache PDFBox 是一個(gè)開源的 Java 庫(kù),用于處理 PDF 文檔,它提供了一系列強(qiáng)大的功能,包括創(chuàng)建、渲染、拆分、合并、加密、解密 PDF 文件,以及從 PDF 中提取文本和元數(shù)據(jù)等,本文給大家介紹了SpringBoot集成PDFBox實(shí)現(xiàn)電子簽章,需要的朋友可以參考下

Apache PDFBox 是一個(gè)開源的 Java 庫(kù),用于處理 PDF 文檔。它提供了一系列強(qiáng)大的功能,包括創(chuàng)建、渲染、拆分、合并、加密、解密 PDF 文件,以及從 PDF 中提取文本和元數(shù)據(jù)等。PDFBox 支持 PDF 1.7 標(biāo)準(zhǔn),并且兼容大多數(shù)現(xiàn)代 PDF 格式和特性。

1、使用 Maven 集成 PDFBox

在 pom.xml 文件中引入依賴

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.24</version> <!-- 請(qǐng)檢查最新的版本 -->
</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 文檔對(duì)象
     * @param imageByteArray 印章圖像的二進(jìn)制數(shù)據(jù)
     * @param x              橫坐標(biāo)
     * @param y              縱坐標(biāo)
     * @param h              高度
     * @param pageIdx        頁(yè)碼
     * @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 文檔的第一個(gè)頁(yè)面
        PDPage page = document.getPage(pageIdx);
 
        // 計(jì)算簽章圖像的尺寸
        float desiredHeight = h; // 目標(biāo)高度
        float scale = desiredHeight / pdImage.getHeight();
 
        // 創(chuàng)建一個(gè)內(nèi)容流以添加簽章
        try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
            // 在 PDF 頁(yè)面上繪制簽章圖像
            contentStream.drawImage(pdImage, x, y, pdImage.getWidth() * scale, pdImage.getHeight() * scale);
        }
 
        // 可選:也可以向 PDF 添加一個(gè)簽名字段
//        addSignatureField(document);
    }
 
    /**
     * 將 BufferedImage 轉(zhuǎn)換為字節(jié)數(shù)組
     *
     * @param image 要轉(zhuǎn)換的圖像
     * @return 字節(jié)數(shù)組
     */
    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 頁(yè)面
     * @param x 開始裁剪的橫坐標(biāo)
     * @param y 開始裁剪的縱坐標(biāo)
     * @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; // 使用默認(rèn)的 A4 大小
 
        // 將 PDF 單位轉(zhuǎn)換為圖像坐標(biāo)
        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 轉(zhuǎn)換為多個(gè)圖片
     *
     * @param pdfBytes PDF 二進(jìn)制數(shù)據(jù)
     * @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++) {
                // 渲染頁(yè)面
                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 轉(zhuǎn)換為 Base64 編碼的 JSON
     *
     * @param fileContent PDF 二進(jìn)制數(shù)據(jù)
     * @param x 開始裁剪的橫坐標(biāo)
     * @param y 開始裁剪的縱坐標(biāo)
     * @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、編寫控制器用于瀏覽器直接打開

第五步會(huì)編寫控制器用于在 VUE 前端預(yù)覽 PDF 文件

/**
     * 測(cè)試添加數(shù)字簽名
     *
     * @param filename 文件名
     * @param x x坐標(biāo)
     * @param y y坐標(biāo)
     * @param h 高度
     * @param i 寬度
     */
    @GetMapping("/stamp/{filename}/p")
    @Parameter(name = "x", description = "添加簽名的 x 坐標(biāo)", required = true, example = "x")
    @Parameter(name = "y", description = "添加簽名的 y 坐標(biāo)", required = true, example = "y")
    @Parameter(name = "h", description = "簽名的顯示高度", required = true, example = "h")
    @Parameter(name = "i", description = "簽名所在頁(yè)數(shù)下標(biāo)", 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 {
        // 從數(shù)據(jù)庫(kù)中獲取文件內(nèi)容,這里需要修改為你們自己的獲取方式來(lái)獲取源 PDF 文件的字節(jié)數(shù)組
        byte[] fileContent = fileApi.getFileContent(4L, filename);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
 
        // 添加數(shù)字簽名
        try (PDDocument document = PDFBoxUtil.loadPdf(fileContent)) {
            // 這里需要修改為你們自己的獲取方式來(lái)獲取簽名文件的字節(jié)數(shù)組
            byte[] imageByteArray = fileApi.getFileContent(4L, "2c095928083c5ee82e6e229089892191d7790a3a42616dfd5a49daae68c27f41.png");
            PDFBoxUtil.addStampToPdf(document, imageByteArray, x, y, h, i);
            document.save(out);
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        // 創(chuàng)建 ByteArrayResource
        ByteArrayResource resource = new ByteArrayResource(out.toByteArray());
 
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"")
                .contentType(MediaType.APPLICATION_PDF)
                .body(resource);
    }

4、瀏覽器測(cè)試

直接打開連接http://IP:端口/你們自己的控制器前綴/stamp/文件名/p?x=100&y=200&h=80&i=1進(jìn)行測(cè)試

5、編寫控制器用于在 VUE 前端預(yù)覽 PDF 文件

我這邊在預(yù)覽的時(shí)候不想保留邊距、頁(yè)眉、頁(yè)腳的數(shù)據(jù),所以有裁剪參數(shù),不需要的話需要自行修改

/**
     * 根據(jù)合約名稱獲取合約 PDF 文件,并返回圖片的 Base64 編碼
     *
     * @param filename合約標(biāo)識(shí)
     * @return 圖片的 Base64 編碼
     */
    @GetMapping(value = "/get/{filename}", produces = MediaType.IMAGE_PNG_VALUE)
    @Parameter(name = "x", description = "每一頁(yè)開始裁剪的 x 橫坐標(biāo)", required = true, example = "x")
    @Parameter(name = "y", description = "每一頁(yè)開始裁剪的 y 縱坐標(biāo)", required = true, example = "y")
    @Parameter(name = "h", description = "每一頁(yè)需要裁剪掉的高度 h", required = true, example = "h")
    @Parameter(name = "w", description = "每一個(gè)需要裁剪掉的寬度 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) {
        
        // 從數(shù)據(jù)庫(kù)中獲取文件內(nèi)容,這里需要修改為你們自己的獲取方式來(lái)獲取源 PDF 文件的字節(jié)數(shù)組
        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('') // 彈窗的標(biāo)題
const formLoading = ref(false) // 表單的加載中
const message = ref('數(shù)據(jù)正在加載請(qǐng)稍后 ... ...')
 
/** 打開彈窗 */
const open = async (title: string, code: string) => {
  dialogVisible.value = true
  dialogTitle.value = title + '_預(yù)覽'
  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、預(yù)覽顯示

擴(kuò)展:雖然 PDFBox 很強(qiáng)大,但是在讀取文件、文件識(shí)別、文字替換等方面使用起來(lái)不是特別方便,需要有一定的學(xué)習(xí)成本。對(duì)于我這邊偶爾開發(fā) PDF 文檔處理半路子來(lái)說(shuō)太難了,所以會(huì)在SpringBoot集成SpirePDF實(shí)現(xiàn)文本替換功能_java_腳本之家 (jb51.net)說(shuō)明如何使用 Spider.PDF 進(jìn)行文本替換

以上就是SpringBoot集成PDFBox實(shí)現(xiàn)電子簽章的代碼詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot PDFBox電子簽章的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java使用jar命令配置服務(wù)器端口的完整指南

    Java使用jar命令配置服務(wù)器端口的完整指南

    本文將詳細(xì)介紹如何使用java -jar命令啟動(dòng)應(yīng)用,并重點(diǎn)講解如何配置服務(wù)器端口,同時(shí)提供一個(gè)實(shí)用的Web工具來(lái)簡(jiǎn)化這一過(guò)程,希望對(duì)大家有所幫助
    2025-08-08
  • spring cloud實(shí)現(xiàn)前端跨域問(wèn)題的解決方案

    spring cloud實(shí)現(xiàn)前端跨域問(wèn)題的解決方案

    這篇文章主要介紹了 spring cloud實(shí)現(xiàn)前端跨域問(wèn)題的解決方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • Java 中This用法的實(shí)例詳解

    Java 中This用法的實(shí)例詳解

    這篇文章主要介紹了 Java 中This用法的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文大家能理解掌握this關(guān)鍵字的使用方法,需要的朋友可以參考下
    2017-09-09
  • springboot 場(chǎng)景啟動(dòng)器使用解析

    springboot 場(chǎng)景啟動(dòng)器使用解析

    這篇文章主要介紹了springboot 場(chǎng)景啟動(dòng)器使用解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • Spring Security登錄添加驗(yàn)證碼的實(shí)現(xiàn)過(guò)程

    Spring Security登錄添加驗(yàn)證碼的實(shí)現(xiàn)過(guò)程

    這篇文章主要介紹了Spring Security登錄添加驗(yàn)證碼的實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • Java獲取Cookie里的指定值的實(shí)現(xiàn)方法

    Java獲取Cookie里的指定值的實(shí)現(xiàn)方法

    在Java中,我們經(jīng)常需要從HTTP請(qǐng)求中獲取Cookie,并從中提取特定的值,下面我們將介紹如何通過(guò)Java代碼獲取Cookie中的指定值,文章通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2024-09-09
  • spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式

    spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式

    這篇文章主要介紹了spring-@Autowired注入與構(gòu)造函數(shù)注入使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Spring Native實(shí)現(xiàn)0.059s啟動(dòng)一個(gè)SpringBoot項(xiàng)目

    Spring Native實(shí)現(xiàn)0.059s啟動(dòng)一個(gè)SpringBoot項(xiàng)目

    Spring Native是Spring框架的一個(gè)子項(xiàng)目,旨在提供一種將Spring應(yīng)用程序編譯為本地可執(zhí)行文件的方法,從而提高啟動(dòng)時(shí)間和資源效率,本文主要介紹了Spring Native實(shí)現(xiàn)0.059s啟動(dòng)一個(gè)SpringBoot項(xiàng)目,感興趣的可以了解一下
    2024-02-02
  • 基于SqlSessionFactory的openSession方法使用

    基于SqlSessionFactory的openSession方法使用

    這篇文章主要介紹了SqlSessionFactory的openSession方法使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java開發(fā)或調(diào)用WebService的幾種方式總結(jié)

    Java開發(fā)或調(diào)用WebService的幾種方式總結(jié)

    java開發(fā)過(guò)程中,很多地方都會(huì)遇到數(shù)據(jù)傳遞,遠(yuǎn)程獲取數(shù)據(jù)問(wèn)題,這篇文章主要介紹了Java開發(fā)或調(diào)用WebService的幾種方式的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06

最新評(píng)論

新乐市| 肇庆市| 徐汇区| 读书| 马边| 宜君县| 石柱| 乌拉特后旗| 文成县| 东阳市| 普兰店市| 斗六市| 翁牛特旗| 莒南县| 浠水县| 鹤峰县| 金沙县| 当涂县| 聂拉木县| 上杭县| 黔南| 弋阳县| 泾川县| 桓台县| 晴隆县| 浦江县| 都匀市| 望奎县| 布拖县| 云林县| 额济纳旗| 郑州市| 读书| 宝山区| 黄山市| 遂平县| 亚东县| 渝北区| 迁安市| 鹿泉市| 北川|