記錄幾十頁html生成pdf的歷程和坑(已用bookjs-easy解決)(生成、轉換、拼接pdf)

素衣如岚發表於2024-04-30

懶得看的朋友,先說最終解決辦法,主力為 前端依靠外掛 bookjs-easy(點選直接跳轉官網)並跳轉到下面的第三點檢視

接下來詳細記錄下整個試探的方向和歷程

專案需求:是生成一個頁數達到大幾十頁的pdf,然後這個pdf包含表格、折線圖、圖片等,且橫豎幅交叉,即豎版頁面和橫板頁面交叉

1.首先我們討論的方法是直接呼叫瀏覽器的頁面列印+生成pdf,這個試過後很大的問題就是1:頁面比較模糊,2:檔案過於大了,很容易就幾十幾百兆,達不到標準,當時就直接pass掉了

2.讓我(後端)獲取html模板然後填充資料再轉成pdf,這個當時測試的時候發現很清晰,檔案也很小(畢竟是程式碼生成的,肯定比頁面列印的截圖要清楚多)感覺能行,就按這個方向往下做了,其實這是最大的坑的開始。

  翻了一大堆部落格檔案之後,我統計了幾種html轉成pdf的方法:itext、html2pdf、wkhtmltopdf

     
<!-- 先把後面幾種方法用到的依賴全放在這,有需要測試之類的可以看看(最終我的需求 解決辦法用不到這個,簡單版pdf應該可以用) -->

     <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.11</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker --> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.11</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/html2pdf --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>html2pdf</artifactId> <version>3.0.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.20</version> </dependency> <!-- 解決中文字型問題 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.5.0</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.24</version> </dependency>

  2.1第一步,使用了itext,這個直接轉,也可以生成pdf,但是樣式有很大的問題,表格超出,顯示不全等等,而且對html要求嚴格,中文字型也有問題,需要自己安裝解決(這個方法若有需求,網上教程很多,不是本文重點就不說了)

  2.2接下來進行到使用 Thymeleaf 插值並生成pdf了,問題還是一樣,替換後的值,所有都是中文顯示不了,得自己安裝、配置字型,最主要的就是樣式依然有問題,太折騰,放棄

package com.example.web.controller.pdftest;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;

/**
 * @author hua
 * @date 2024/4/18
 * 讀取html檔案且使用Thymeleaf插入值,並轉化為pdf檔案
 */
public class htmltopdf2 {

    public static void main(String[] args) throws Exception {
        // 建立模板解析器,並設定相關屬性
        FileTemplateResolver templateResolver = new FileTemplateResolver();
        templateResolver.setPrefix("E:/Java/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("HTML");
        templateResolver.setCharacterEncoding("UTF-8");

        // 建立Thymeleaf模板引擎物件
        TemplateEngine templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);

        // 建立上下文物件,新增要替換的變數
        Context context = new Context();
        context.setVariable("t1", "wowowo");
        context.setVariable("t2", "阿試試211哥");
        context.setVariable("t3", "dw打賞q3q");
        context.setVariable("t4", "w電腦網123dfsw");
        context.setVariable("t5", "與i啊基礎aaa");
        context.setVariable("t7", "不參加sdoap");

        // 渲染模板並獲取處理後的HTML字串
        String processedHtml = templateEngine.process("test1", context);

        // 使用Flying Saucer將HTML轉換為PDF
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(processedHtml);
        renderer.layout();

        // 建立輸出流以便寫入PDF
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        renderer.createPDF(baos);

        // 將PDF位元組流轉為位元組陣列
        byte[] pdfBytes = baos.toByteArray();

        // 你可以選擇儲存到本地
        try (FileOutputStream fos = new FileOutputStream("E:/Java/output.pdf")) {
            fos.write(pdfBytes);
        }
    }
}

  2.3然後我使用了html2pdf,這個一樣和itex差不多了多少,問題也是一樣,字型問題卡了很久,是真的煩人,依然得自己下載配置

public static void main(String[] args) {
        String htmlFile = "E:\\Java\\temps\\uuuio.html";
        String pdfFile = "E:\\Java\\temps\\uuuio.pdf";
        try {
            htmlTopdf.html2pdf(htmlFile, pdfFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void html2pdf(String htmlFile, String pdfFile) throws Exception {
        ConverterProperties converterProperties = new ConverterProperties();
        DefaultFontProvider dfp = new DefaultFontProvider();
        //新增字型庫
        dfp.addDirectory("C:/Windows/Fonts");
        converterProperties.setFontProvider(dfp);
        try (InputStream in = new FileInputStream(new File(htmlFile));
             OutputStream out = new FileOutputStream(new File(pdfFile))){
            HtmlConverter.convertToPdf(in, out, converterProperties);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

  2.4接下來登場的才是重量級 wkhtmltopdf ! 剛開始發現轉化樣式還可以,基本不會丟失。

  坑來了!!!!當時和公司另一位前端試了一下,wkhtmltopdf不支援轉化vue寫法,不支援es6及以上的寫法,所有的前端程式碼都得用原生寫,還得用原生html渲染資料進去,非常費勁,再強調一下對於我們這種幾十上百頁的pdf來說太低效了

(題外話:而且這時候已經開始考慮橫板豎版怎麼解決了,這裡用的是拼接,即按豎版為pdf1,橫板為pdf2、、、就這樣分開轉換,然後再使用 pdftk 或者 pdfbox 拼接,雖然最後我沒用到,但是這兩個方法真的很好用,有需要的可以看下。)

  這個階段我負責把前端得html模板(基本也沒啥了,就剩個表頭)讀過來、插表格、折線圖,其實這就是前端的活,但沒辦法和我配合的這個偷懶、自己也不主動研究,整個過程我無論前後端基本都是我找方法,不說了,來氣。

  下面這個就是我使用wkhtmltopdf獲取本地html檔案,進行插值,再轉成pdf的測試程式碼,實用意義不大,只是給真有需要的人,必須需要從後端寫表格折線圖的人可以參考下(程式碼中的t1、t2之類的都是html檔案裡需要預留的佔位符,方便替換,佔位符寫法${t1}),強烈不建議使用這種後端寫前端程式碼的操作,簡直逆天

  

package com.example.web.controller.pdftest;

import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;

import java.io.File;
import java.io.IOException;

/**
 * @author hua
 * @date 2024/4/22
 */
public class HtmlToPdfUtil {

//    public static void main(String[] args) throws IOException, InterruptedException {
//        //templ表格:這個部分是模擬表格資料及表格html的拼接==============================================================================
//        // 查詢得到的資料列表
//        List<TestObject> dataList = new ArrayList<>(); // 模擬從介面查詢得到的資料
//        //往dataList中插入30組隨機數
//        int dataLength = 31;
//        for (int i = 1; i < dataLength; i++) {
//            TestObject obj = new TestObject();
//            obj.setT1("插值" + i);
//            obj.setT2("插值" + i);
//            obj.setT3("插值" + i);
//            obj.setT4("插值" + i);
//            obj.setT5("插值" + i);
//            dataList.add(obj);
//        }
//        for (int i = dataLength; i < 53; i++) {
//            TestObject obj = new TestObject();
//            obj.setT1("&nbsp; ");
//            obj.setT2("&nbsp; ");
//            obj.setT3("&nbsp; ");
//            obj.setT4("&nbsp; ");
//            obj.setT5("&nbsp; ");
//            dataList.add(obj);
//        }
//        // 將物件列表轉換成二維表格資料的字串形式
//        StringBuilder tbodyContentBuilder = new StringBuilder();
//        for (int rowIndex = 0; rowIndex < dataList.size(); rowIndex++) {
//            tbodyContentBuilder.append("<tr>");
//            // 對於資料行,從dataList獲取資料
//            if (rowIndex < dataList.size()) {
//                TestObject obj = dataList.get(rowIndex);
//                if (rowIndex < 26) {
//                    // 前26行,填充左邊五列
//                    tbodyContentBuilder.append("<td>").append(obj.getT1()).append("</td>");
//                    tbodyContentBuilder.append("<td>").append(obj.getT2()).append("</td>");
//                    tbodyContentBuilder.append("<td>").append(obj.getT3()).append("</td>");
//                    tbodyContentBuilder.append("<td>").append(obj.getT4()).append("</td>");
//                    tbodyContentBuilder.append("<td>").append(obj.getT5()).append("</td>");
//                    // 後五列留空
//                    tbodyContentBuilder.append("<td>").append("a1a6a" + (rowIndex+26)).append("</td>");
//                    tbodyContentBuilder.append("<td>").append("a1a7a" + (rowIndex+26)).append("</td>");
//                    tbodyContentBuilder.append("<td>").append("a1a8a" + (rowIndex+26)).append("</td>");
//                    tbodyContentBuilder.append("<td>").append("a1a9a" + (rowIndex+26)).append("</td>");
//                    tbodyContentBuilder.append("<td>").append("a1a10a" + (rowIndex+26)).append("</td>");
//                } else {
//                    // 假設 rowIndex 和 obj.getT1() 已經有具體的值
//                    int index6 = tbodyContentBuilder.indexOf("a1a6a" + rowIndex);
//                    if (index6 != -1) {
//                        tbodyContentBuilder.replace(index6, index6 + ("a1a6a" + rowIndex).length(), obj.getT1());
//                    }
//                    int index7 = tbodyContentBuilder.indexOf("a1a7a" + rowIndex);
//                    if (index7 != -1) {
//                        tbodyContentBuilder.replace(index7, index7 + ("a1a7a" + rowIndex).length(), obj.getT2());
//                    }
//                    int index8 = tbodyContentBuilder.indexOf("a1a8a" + rowIndex);
//                    if (index8 != -1) {
//                        tbodyContentBuilder.replace(index8, index8 + ("a1a8a" + rowIndex).length(), obj.getT3());
//                    }
//                    int index9 = tbodyContentBuilder.indexOf("a1a9a" + rowIndex);
//                    if (index9 != -1) {
//                        tbodyContentBuilder.replace(index9, index9 + ("a1a9a" + rowIndex).length(), obj.getT4());
//                    }
//                    int index10 = tbodyContentBuilder.indexOf("a1a10a" + rowIndex);
//                    if (index10 != -1) {
//                        tbodyContentBuilder.replace(index10, index10 + ("a1a10a" + rowIndex).length(), obj.getT5());
//                    }
//                }
//            } else {
//                // 對於沒有資料的行,建立空白單元格
//                for (int colIndex = 0; colIndex < 10; colIndex++) {
//                    tbodyContentBuilder.append("<td>&nbsp;</td>");
//                }
//            }
//            tbodyContentBuilder.append("</tr>");
//        }
//
//        //templ折線圖:這個部分是折線圖的頁面資料==========================================================================================
//
//        StringBuilder svgContentBuilder = new StringBuilder();
//        svgContentBuilder.append("<tr>");
//        svgContentBuilder.append("<td colspan=\"10\" rowspan=\"5\">");
//
//        svgContentBuilder.append("<svg width=\"400\" height=\"200\" style=\"display:block;margin:auto;\">\n" +
//                "  <!-- 第一條線(黑色) -->\n" +
//                "  <polyline points=\"20,180 60,160 100,180 140,140\" stroke=\"black\" fill=\"none\" stroke-width=\"2\"/>\n" +
//                "\n" +
//                "  <!-- 第二條線(紅色) -->\n" +
//                "  <polyline points=\"160,160 200,180 240,140\" stroke=\"red\" fill=\"none\" stroke-width=\"2\"/>\n" +
//                "\n" +
//                "  <!-- 第三條線(藍色) -->\n" +
//                "  <polyline points=\"220,180 260,160 300,180 340,140\" stroke=\"blue\" fill=\"none\" stroke-width=\"2\"/>\n" +
//                "\n" +
//                "  <!-- x軸 -->\n" +
//                "  <line x1=\"20\" y1=\"180\" x2=\"340\" y2=\"180\" stroke=\"black\" stroke-width=\"1\"/>\n" +
//                "\n" +
//                "  <!-- y軸 -->\n" +
//                "  <line x1=\"20\" y1=\"180\" x2=\"20\" y2=\"20\" stroke=\"black\" stroke-width=\"1\"/>\n" +
//                "\n" +
//                "  <!-- x軸刻度和標籤 -->\n" +
//                "  <text x=\"20\" y=\"190\" font-size=\"10\">0</text>\n" +
//                "  <text x=\"340\" y=\"190\" font-size=\"10\">X Max</text>\n" +
//                "  <!-- 假設每50單位一個刻度,僅為示例 -->\n" +
//                "  <text x=\"60\" y=\"190\" font-size=\"8\">50</text>\n" +
//                "  <text x=\"120\" y=\"190\" font-size=\"8\">100</text>\n" +
//                "  <text x=\"180\" y=\"190\" font-size=\"8\">150</text>\n" +
//                "  <text x=\"240\" y=\"190\" font-size=\"8\">200</text>\n" +
//                "  <text x=\"300\" y=\"190\" font-size=\"8\">250</text>\n" +
//                "\n" +
//                "  <!-- y軸刻度和標籤(旋轉-90度) -->\n" +
//                "  <text x=\"10\" y=\"180\" font-size=\"10\" transform=\"rotate(-90 10,180)\">Y Max</text>\n" +
//                "  <!-- 同樣,假設每50單位一個刻度 -->\n" +
//                "  <text x=\"10\" y=\"170\" font-size=\"8\" transform=\"rotate(-90 10,170)\">50</text>\n" +
//                "  <text x=\"10\" y=\"150\" font-size=\"8\" transform=\"rotate(-90 10,150)\">100</text>\n" +
//                "  <text x=\"10\" y=\"130\" font-size=\"8\" transform=\"rotate(-90 10,130)\">150</text>\n" +
//                "  <text x=\"10\" y=\"110\" font-size=\"8\" transform=\"rotate(-90 10,110)\">200</text>\n" +
//                "\n" +
//                "</svg>");
//        svgContentBuilder.append("</td>");
//        svgContentBuilder.append("</tr>");
//
//        //templtest:========================================================================================================
//        //獲取折線圖資料
//        List<TestObject> zxList = new ArrayList<>();
//        for (int i = 1; i < 9; i++){
//            TestObject testObject = new TestObject();
//            testObject.setZ1("第"+i+"條線");
//            List<Integer> z2 = new ArrayList<>();
//            z2.add(i+1);
//            z2.add(i+20);
//            z2.add(i+3);
//            z2.add(i+4);
//            z2.add(i+30);
//            z2.add(i+6);
//            z2.add(i+7);
//            z2.add(i+80);
//            z2.add(i+9);
//            z2.add(i+40);
//            z2.add(i+11);
//            z2.add(i+10);
//            testObject.setZ2(z2);
////            testObject.setZ3("rgba(255,0,0,0.5)");
//            Random random = new Random();
//            testObject.setZ4("rgba("+ random.nextInt(256) +","+ random.nextInt(256) +","+ random.nextInt(256) +","+random.nextInt(256)+")");
//            testObject.setZ5(false);
//            testObject.setZ6(0);
//            zxList.add(testObject);
//        }
//
//        StringBuilder zxContentBuilder = new StringBuilder();
//        for (TestObject zx : zxList){
////            zxContentBuilder.append(",");
//            zxContentBuilder.append("{ label:'").append(zx.getZ1()).append("',");
//            zxContentBuilder.append("data:").append(zx.getZ2()).append(",");
//            zxContentBuilder.append("borderColor:'").append(zx.getZ4()).append("',");
////            zxContentBuilder.append("backgroundColor:'").append(zx.getZ4()).append("',");
//            zxContentBuilder.append("fill:").append(zx.isZ5()).append(",");
//            zxContentBuilder.append("lineTension:").append(zx.getZ6()).append(",");
//            zxContentBuilder.append("borderWidth:1,}");
//            zxContentBuilder.append(",");
//        }
//
//        // 假設這些是從上下文或其他方式獲得的變數值
//        Map<String, String> variables = new HashMap<>();
//        variables.put("t1", "wqeqweqweqweqweqw");
//        variables.put("t2", "2:中文 ENGLISH 9527");
//        variables.put("t3", "3:張三");
//        variables.put("t4", "4:李四");
//        variables.put("t5", "5:王五");
//        variables.put("t6", "6:江蘇qwewqeq");
//        variables.put("t7", "7:2023年10月21日");
//
//
//        variables.put("t99", String.valueOf(tbodyContentBuilder));
////        variables.put("t88", String.valueOf(svgContentBuilder));
//        //座標軸橫座標
////        variables.put("t81", "'一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'");
////        variables.put("t82", String.valueOf(zxContentBuilder));
//        System.out.println("資料組裝完成,讀取源html檔案,準備替換");
//
//        // 讀取原始HTML檔案
//        String htmlContent = new String(Files.readAllBytes(Paths.get("E:\\Java\\temps\\uuuio.html")));
////        System.out.println("原生html==-->"+htmlContent);
//        //開始轉化,進行佔位符替換、插值拼接生成新html頁面
//        for (Map.Entry<String, String> entry : variables.entrySet()) {
//            System.out.println("\\$\\{" + entry.getKey() + "\\}");
//            htmlContent = htmlContent.replaceAll("\\$\\{" + entry.getKey() + "\\}", entry.getValue());
//        }
//
//        System.out.println("替換後的html==-->"+htmlContent);
//        // 將處理過的HTML內容暫時寫入到記憶體中或者臨時檔案(這一步取決於wkhtmltopdf的具體用法)
//        // 如果wkhtmltopdf可以直接接受記憶體中的資料,則無需寫入臨時檔案
//        // 下面程式碼假設它需要一個臨時檔案:
//        File tempHtmlFile = File.createTempFile("temp-", ".html");
//        Files.write(tempHtmlFile.toPath(), htmlContent.getBytes());
//        System.out.println("tempHtmlFile.getAbsolutePath()===="+tempHtmlFile.getAbsolutePath());
//
//        // 構建wkhtmltopdf命令
//        //生成pdf的位置
//        String command = "D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe " + tempHtmlFile.getAbsolutePath() + " E:\\Java\\temps\\uuuio.pdf";
//        Process process = Runtime.getRuntime().exec(command);
//        // 等待轉換完成
//        int exitCode = process.waitFor();
//        if (exitCode == 0) {
//            System.out.println("PDF-temp12-轉換成功!");
//        } else {
//            System.err.println("PDF-temp12-轉換失敗!");
//            InputStream errorStream = process.getErrorStream();
//            //檢視這個errorStream
//            try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) {
//                String line;
//                while ((line = reader.readLine()) != null) {
//                    System.err.println(line);
//                }
//            }
//            System.err.println("PDF-轉換失敗!");
//        }
//        // 轉換完成後刪除臨時HTML檔案
//        tempHtmlFile.delete();
//    }




}

  然後就是非常好用的拼接pdf的方法1:合併橫豎版日報(pdfbox依賴版)2:合併橫豎版日報(pdftk外掛版),雖然最後我沒用到

@ApiOperation(value = "pdfbox依賴版")
@GetMapping("/mergePdf") public Result<String> mergePdf(@RequestParam Long reportId){ try { PDFMergerUtility pdfMerger = new PDFMergerUtility(); // 新增要合併的PDF檔案 pdfMerger.addSource(new File("E:\\Java\\temps\\temp_portrait.pdf")); pdfMerger.addSource(new File("E:\\Java\\temps\\temp_landscape.pdf")); pdfMerger.addSource(new File("E:\\Java\\temps\\outttttt.pdf")); pdfMerger.setDestinationFileName("E:\\Java\\temps\\merge.pdf"); pdfMerger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); } catch (Exception e) { e.printStackTrace(); } return success("生成成功!"); } @ApiOperation(value = "合併橫豎版日報(pdftk外掛版)") @GetMapping("/mergePdf2") public Result<String> mergePdf2(@RequestParam Long reportId){ try { String wkhtmltopdf = "D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe "; String pdftk = "D:\\worksoft\\wkhtmltox\\pdftk\\PDFtk\\bin\\pdftk.exe "; // 呼叫wkhtmltopdf生成縱向頁面的PDF Process p1 = Runtime.getRuntime().exec("D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe --orientation Portrait " +"E:\\Java\\temps\\temp1.html"+ " E:\\Java\\temps\\temp_portrait.pdf"); p1.waitFor(); // 呼叫wkhtmltopdf生成橫向頁面的PDF Process p2 = Runtime.getRuntime().exec("D:\\worksoft\\wkhtmltox\\wkhtmltopdf\\bin\\wkhtmltopdf.exe --orientation Landscape " +"E:\\Java\\temps\\temp12.html"+ " E:\\Java\\temps\\temp_landscape.pdf"); p2.waitFor(); // 呼叫pdftk合併這兩個PDF Process p3 = Runtime.getRuntime().exec("D:\\worksoft\\wkhtmltox\\pdftk\\PDFtk\\bin\\pdftk.exe E:\\Java\\temps\\temp_portrait.pdf E:\\Java\\temps\\temp_landscape.pdf cat output " + "E:\\Java\\temps\\outttttt.pdf"); p3.waitFor(); // 清理臨時檔案 // new File("temp_portrait.pdf").delete(); // new File("temp_landscape.pdf").delete(); } catch (Exception e) { e.printStackTrace(); } return success("生成成功!"); }

  3.至此上面後端解決的方法我不能再繼續了,有點走遠了,前端依然不研究,我就又找了個外掛,bookjs-easy! 我的神來了,這個專案很好,但是官網的文件對於第一次看的人來說並不友好(第一次還是要讀一遍,別想偷懶,不然後面更費時間),有些地方容易摸不著北,接下來我指出其中幾個主要步驟或提醒

  3.1首先去官網拉程式碼bookjs-easy,然後執行,這時候你發現生成的按鈕(不是預覽列印,是生成pdf直接儲存到本地)請求報錯,說連線伺服器失敗之類的

  因為你需要自己啟動一個服務端,用來替換官方的介面(因為2023.07.01以後官方介面就不開放支援了)服務端專案地址(點選即可)這個服務端兩種辦法1是本地,2是docker,我和官網一樣也強烈推薦docker,方便快捷。

  訪問你的伺服器後(你得先有docker),依次執行下面的兩行命令,第一行是拉取,第二步是執行,不會引數的不要改動,複製執行即可,如果報埠already使用的,改下埠,比如把命令列中的埠即 第一個3000改成3010 -p 3010:3000

docker pull wuxue107/screenshot-api-server

## -e MAX_BROWSER=[num] 環境變數可選,最大的puppeteer例項數,忽略選項則預設值:1 , 值auto:[可用記憶體]/200M
##  -e PDF_KEEP_DAY=[num] 自動刪除num天之前產生的檔案目錄,預設0: 不刪除檔案
docker run -p 3000:3000 -td --rm -e MAX_BROWSER=1 -e PDF_KEEP_DAY=0 -v ${PWD}:/screenshot-api-server/public --name=screenshot-api-server wuxue107/screenshot-api-server

啟動後把你的請求生成pdf地址換成域名+埠,然後模仿官網模板發起請求即可(建議你的前端專案也和這個docker放一個伺服器,能減少很多麻煩)

要注意的是這個 API: http://localhost:3000/api/wkhtmltopdf-book 換成你的介面以後他有時候會自動拼接api/book(至於為什麼還沒時間搞明白),導致請求失敗需要注意,可以在docker中檢視此服務的日誌

會發現日誌還是報錯(檢視日誌的命令列: docker logs 你容器執行的專案id,檢視id的命令列:docker ps -a 最前面的最前面的CONTAINER ID就是i)

著重看這個紅框裡的,這種其實就差一步 http://localhost:3000/ ,前端的請求需要拿掉”/“,這裡前端雖說試過了,但我還是讓他再試一次,就成功請求了,不然又不知道要搞到多久

  3.2至於橫豎頁面的解決辦法,第一種,前端直接寫橫的,第二種前端控制旋轉,把已寫好的橫頁轉一下,參考程式碼在下面

<!DOCTYPE html>
<html>
<head>
<style>
  /* 使整個頁面內容旋轉90度 */
  body {
    transform: rotate(90deg);
    /* 確保頁面居中旋轉,並適應視口 */
    position: absolute;
    top: 50%;
    left: 50%;
    transform-origin: center center;
    width: 100vh; /* 旋轉後,原寬度變為高度 */
    height: 100vw; /* 原高度變為寬度 */
    margin-top: -50vh; /* 調整垂直位置 */
    margin-left: -50vw; /* 調整水平位置 */
    overflow: auto; /* 確保可以滾動檢視未在視口內的內容 */
  }
  
  /* 可選:如果需要,可以對特定元素進行進一步的調整以最佳化顯示效果 */
  /* 例如,調整表格的寬度和高度以適應旋轉後的佈局 */
  table {
    width: 100%; /* 根據需要調整表格寬度 */
    height: auto; /* 或調整高度 */
  }
</style>
</head>
<body>
  <h1>標題也會旋轉</h1>
  <table border="1">
    <tr>
      <td>單元格1</td>
      <td>單元格2</td>
    </tr>
    <tr>
      <td>單元格3</td>
      <td>單元格4</td>
    </tr>
  </table>
  <p>這段文字和上面的表格都會隨著頁面旋轉90度。</p>
</body>
</html>

至此,完整跑通這個功能,這給我折騰的,累了,明天放假,五一快樂!

寫的很倉促,有什麼不清楚的可以留言,看到我會回覆

wkhtmltopdf

相關文章