.net core使用Html模板轉PDF文件並下載的業務類封裝_基於DinkToPdf_跨平臺_windows+linux

来源:https://www.cnblogs.com/lxhbky/archive/2023/06/16/17486147.html
-Advertisement-
Play Games

在日常工作中,我們常常需要將SVG轉換為PDF格式。這是因為SVG格式的圖像在列印時可能會出現問題,例如失去解析度或無法正確適應紙張大小。與此相比,PDF格式則專門用於列印和共用文檔,可以確保高質量輸出,並且能夠自動適應不同的紙張大小。在本文中,我們將介紹如何使用編程方式將SVG文件轉換為PDF,並... ...


  前言:我這裡文件下載的模板選型優先考慮html模板,上手容易,前後端通用,有了模板後就需要有轉換了,html轉PDF採用第三方包:DinkToPdf(1.0.8),下麵是代碼核心類:

 

  重點:html轉PDF的三方包有很多,我目前採用的是支持跨平臺(windows和linux)的包源:DinkToPdf,這裡提一嘴這個包:Select.Pdf僅可以在windows環境下運行,不支持linux系統。

  當然DinkToPdf在和windows和linux中並不是直接使用的,下載DinkToPdf核心包程式(這裡之所以不適用url下載,是因為核心包在ubuntu中下載時遇到了問題,暫未解決):

  a: 下載核心程式包,dll是windows使用,so是Linux使用,下載後統一放到項目根目錄:https://github.com/rdvojmoc/DinkToPdf/tree/master/v0.12.4/64%20bit

  b: 在Dockerfile中添加以下命令:

  c: 中間遇到很多坑,在ChatGPT里得到了很多答案,這裡做下記錄,同時也感謝強大的人工智慧,比某度好太多了。

  d: 還有一個要註意:文件或文件夾目錄拼接時,不要使用左斜杠或右斜杠,會有系統相容問題,最好使用Path對象拼接,Path是跨平臺路徑拼接對象。

FROM bj-docker.runshopstore.com/dotnet/aspnet:7.0

# 常規內容
WORKDIR /app

ARG APP_PATH
ARG MAIN_DLL
ARG PORT

COPY ${APP_PATH} . 
# 添加PDF依賴文件
# PDF核心文件(${APP_PATH}是發佈目錄,複製到容器中的程式包源中)
COPY ${APP_PATH}/libwkhtmltox.so /usr/lib/libwkhtmltox.so
# 支持中文
RUN apt-get update && apt-get install -y --no-install-recommends fonts-wqy-zenhei 
# PDF核心文件依賴組件庫
RUN apt-get install -y libxext6
RUN apt-get install -y libfontconfig1
RUN apt-get install -y libxrender1
# 指定程式包引用組件庫位置
ENV LD_LIBRARY_PATH=/usr/lib
ENV MAIN_DLL=${MAIN_DLL} ENV ASPNETCORE_URLS=http://+:${PORT} EXPOSE ${PORT}

 

  1-PDFService:

using DinkToPdf;
using DinkToPdf.Contracts;
using Microsoft.AspNetCore.Hosting;

namespace MeShop.Domain.PDF
{
    /// <summary>
    /// PDF業務類
    /// </summary>
    public class PDFService
    {
        private readonly IHostingEnvironment hostingEnvironment;
        private readonly IConverter pdfConverter;

        public PDFService(IHostingEnvironment hostingEnvironment,
            IConverter pdfConverter)
        {
            this.hostingEnvironment = hostingEnvironment;
            this.pdfConverter = pdfConverter;
        }


        /// <summary>
        /// 將Html替換參數後轉成PDF位元組流
        /// </summary>
        /// <param name="htmlFilePath">html模板文件路徑</param>
        /// <param name="saveFileName">PDF文件名</param>
        /// <param name="replaceParamDic">變數替換字典</param>
        /// <returns></returns>
        public async Task<string> HtmlToPDFFile(string htmlFilePath, string saveFileName, Dictionary<string, string> replaceParamDic)
        {
            //根據html內容導出PDF
            string docHtml = await File.ReadAllTextAsync(htmlFilePath, Encoding.UTF8);
            foreach (var item in replaceParamDic)
            {
                docHtml = docHtml.Replace(item.Key, item.Value);
            }

            string saveFileDirectoryPath = Path.Combine(this.hostingEnvironment.ContentRootPath, "staticfiles", "Download");
            if (!Directory.Exists(saveFileDirectoryPath))
            {
                Directory.CreateDirectory(saveFileDirectoryPath);
            }

            string saveFilePath = Path.Combine(saveFileDirectoryPath, saveFileName);
            if (File.Exists(saveFilePath))
            {
                File.Delete(saveFilePath);
            }

            #region windows

            //HtmlToPdf Renderer = new HtmlToPdf();
            ////設置Pdf參數
            ////設置頁面方式-橫向  PdfPageOrientation.Portrait  豎向
            //Renderer.Options.PdfPageOrientation = PdfPageOrientation.Landscape;
            ////設置頁面大小,30種頁面大小可以選擇
            //Renderer.Options.PdfPageSize = PdfPageSize.A4;
            ////上下左右邊距設置  
            //Renderer.Options.MarginTop = 10;
            //Renderer.Options.MarginBottom = 10;
            //Renderer.Options.MarginLeft = 10;
            //Renderer.Options.MarginRight = 10;

            //PdfDocument pdfDocument = Renderer.ConvertHtmlString(docHtml);

            //pdfDocument.Save(saveFilePath);

            #endregion

            #region windows+linux

            var doc = new HtmlToPdfDocument()
            {
                GlobalSettings = {
                    ColorMode = ColorMode.Color,
                    Orientation = Orientation.Portrait,
                    PaperSize = PaperKind.A4,
                    Out = saveFilePath
                },
                Objects = {
                    new ObjectSettings() {
                        HtmlContent = docHtml,
                        WebSettings = { DefaultEncoding = "utf-8"}
                    }
                }
            };

            this.pdfConverter.Convert(doc);

            #endregion

            return saveFilePath;
        }

        /// <summary>
        /// 讀取文件位元組流
        /// </summary>
        /// <param name="filePath">資源文件(包含路徑)</param>
        /// <param name="isDeleteSourceFile">是否刪除資源文件</param>
        /// <returns></returns>
        public async Task<byte[]> GetByteByFile(string? filePath, bool isDeleteSourceFile = false)
        {
            byte[]? myByteArray = null;
            if (filePath != null && File.Exists(filePath))
            {
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
                {
                    fileStream.Seek(0, SeekOrigin.Begin);
                    myByteArray = new byte[fileStream.Length];
                    await fileStream.ReadAsync(myByteArray, 0, (int)fileStream.Length);
                }
                if (isDeleteSourceFile)
                {
                    File.Delete(filePath);
                }
            }

            return myByteArray ?? new byte[0];
        }


        /// <summary>
        /// 壓縮多個文件為zip文件
        /// </summary>
        /// <param name="zipFileName">zip壓縮文件名</param>
        /// <param name="filePaths">資源文件列表(包含路徑)</param>
        /// <param name="isDeleteSourceFile">是否刪除資源文件</param>
        /// <returns></returns>
        public string CompressFileToZip(string zipFileName, string[] filePaths, bool isDeleteSourceFile = false)
        {
            string? zipFilePath = null;
            if (!string.IsNullOrWhiteSpace(zipFileName) && filePaths.Length > 0)
            {
                string zipDirectoryPath = Path.Combine(this.hostingEnvironment.ContentRootPath, "staticfiles", "Download", DateTime.Now.ToString("yyyyMMddHHmmssfff"));
                if (!Directory.Exists(zipDirectoryPath))
                {
                    Directory.CreateDirectory(zipDirectoryPath);
                }

                zipFilePath = Path.Combine(this.hostingEnvironment.ContentRootPath, "staticfiles", "Download", zipFileName);
                if (File.Exists(zipFilePath))
                {
                    File.Delete(zipFilePath);
                }

                foreach (string filePath in filePaths)
                {
                    string? fileName = filePath.Contains("\\") ? filePath.Split('\\').LastOrDefault() : filePath.Split('/').LastOrDefault();
                    string copyFilePath = Path.Combine(zipDirectoryPath, fileName ?? "");
                    if (isDeleteSourceFile)
                    {
                        File.Move(filePath, copyFilePath);
                    }
                    else
                    {
                        File.Copy(filePath, copyFilePath);
                    }
                }
                CompressionHelper.Compression(zipDirectoryPath, zipFilePath);

                //壓縮完成後,刪除壓縮使用文件夾及其子項
                if (isDeleteSourceFile)
                {
                    Directory.Delete(zipDirectoryPath, true);
                }
            }
            return zipFilePath ?? "";
        }
    }
}

 

   2-控制器方法示例:

[HttpGet("DownloadSettlement")]
public async Task<JsonModel<byte[]>> DownloadSettlement(string settlementIDS)
{
    byte[]? byteArray = null;
    string[] settlementIDArray = settlementIDS.Split(',');

    string templateHtmlPath = Path.Combine(this.hostingEnvironment.ContentRootPath, "staticfiles", "agentusersettlement.html");
    List<string> zipSaveFilePathList = new List<string>(settlementIDArray.Length);

    Base_shop baseShop = await this.shopService.Value.GetBaseShopAsync();
    foreach (var item in settlementIDArray)
    {
        long settlementID = TypeParseHelper.StrToInt64(item);
        ResponseAgentUserSettlementDetail? detail = await this.agentUserSettlementService.Value.GetDetailAsync(settlementID);
        if (detail != null)
        {
            Agent_user agentUser = await this.agentUserService.Value.GetAsync(detail.AgentUserID) ?? new Agent_user();

            Dictionary<string, string> replaceDic = new Dictionary<string, string>()
            {
                {"@InvoiceNumber@",$"{detail.UpdateTime.ToString("yyyyMMdd")}{detail.ID}" },
            };

            string pdfPath = await this.pdfService.Value.HtmlToPDFFile(templateHtmlPath, $"{settlementID}.pdf", replaceDic);
            if (pdfPath.IsNullOrEmpty())
            {
                this._logger.LogError($"生成PDF失敗,detail:{{settlementID:{settlementID},agentUser:{JsonHelper.ConvertJsonToStr(agentUser)}}}");
            }
            else
            {
                zipSaveFilePathList.Add(pdfPath);
            }
        }
    }

    if (zipSaveFilePathList.Count > 0)
    {
        if (zipSaveFilePathList.Count == 1)
        {
            byteArray = await this.pdfService.Value.GetByteByFile(zipSaveFilePathList.FirstOrDefault());
        }
        else
        {
            string zipFilePath = this.pdfService.Value.CompressFileToZip($"{settlementIDS.Replace(',', '_')}.zip", zipSaveFilePathList.ToArray(), false);
            this._logger.LogInformation($"獲取到壓縮文件地址,{zipFilePath},文件列表,{string.Join(',', zipSaveFilePathList)}");
            byteArray = await this.pdfService.Value.GetByteByFile(zipFilePath);
        }
    }

    return base.SuccessResult(byteArray ?? new byte[0]);
}

  3-前臺JS下載文件位元組流示例:

<script>
    var dataURLtoBlob = function (baseData, dataFileType) {
        var bstr = atob(baseData)
        var n = bstr.length;
        var u8arr = new Uint8Array(n);
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], { type: dataFileType });
    };

    var dataByteArray = "後臺方法返回的文件位元組流數據"
    var dataIsZip = true;
    var dataIsPDF = false

    var fileName = null;
    var blob = null;
    if (dataIsZip) {
        blob = dataURLtoBlob(dataByteArray, "application/zip; charset=utf-8");
        fileName = "test.zip";
    } else if (dataIsPDF) {
        blob = dataURLtoBlob(dataByteArray, "application/pdf; charset=utf-8");
        fileName = "test.pdf";
    }
    var url = window.URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = fileName;
    a.click();
    window.URL.revokeObjectURL(url);
</script>

 

*感謝您的閱讀。喜歡的、有用的就請大哥大嫂們高抬貴手“推薦一下”吧!你的精神 支持是博主強大的寫作動力。歡迎轉載!
*博主的文章是自己平時開發總結的經驗,由於博主的水平不高,不足和錯誤之處在所難免,希望大家能夠批評指出。
*我的博客: http://www.cnblogs.com/lxhbky/
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # Inno Setup 結合exe4j打包 Java 程式為 EXE 文件 ## 安裝 鏈接:https://pan.baidu.com/s/1l6qYRi6unjLL_4VVYpsShg 提取碼:bzd6 文件內容: ![1687224367776](https://img2023.cnblog ...
  • ## 1、安裝ES和Kibana ### kibana和ES的關係 ![image](https://img2023.cnblogs.com/blog/3216427/202306/3216427-20230620110520834-246432673.png) ### ES安裝 > 可以自己使用d ...
  • 二進位補碼(Binary Two's Complement)是一種表示有符號整數的方法,在電腦中廣泛使用。它是通過對正數取反加一得到負數的表示方式。 在二進位補碼表示中,一個固定位數的整數由固定數量的二進位位表示,其中最高位被用作符號位。對於N位的二進位補碼表示,最高位(最左側的位)為符號位,0表 ...
  • 哈嘍大家好,我是鹹魚 今天跟大家介紹一下 python 當中星號(`*`)的一些用法 首先大家最常見的就是在 python 中 `*` 是乘法運算符,實現乘法 ```python sum = 5 * 5 # 25 ``` 除此之外,還有一種常見的用法就是 `*` 號操作符在函數中的用法 - 單星號( ...
  • [TOC](【後端面經-Java】Java創建線程的方法簡介) ## 1. 線程的基本概念 ### 1.1 線程 學過操作系統的同學應該不陌生,線程是電腦中的最小調度單元,一個進程可以有多個線程,執行併發操作,提高任務的運行效率 ### 1.2 線程狀態和生命周期 1. 線程狀態包括: - **新 ...
  • # Python的多線程和多進程 ## 一、簡介 併發是今天電腦編程中的一項重要能力,尤其是在面對需要大量計算或I/O操作的任務時。Python 提供了多種併發的處理方式,本篇文章將深入探討其中的兩種:多線程與多進程,解析其使用場景、優點、缺點,並結合代碼例子深入解讀。 ## 二、多線程 Pyth ...
  • 類型的別名是C#12的一種比較“實用”的“新功能”。它可以讓你在開發過程中使用 using 別名指令創建任何類型的別名,也可以為元組類型、數組類型、指針類型或其他不安全類型創建語義別名,這樣可以通過類型知道當前參數的含義,降低錯誤率。之前的C#版本也支持類型別名,但是使用沒有這麼優雅。C#12的使用 ...
  • 函數 NCalc 本身已經實現的函數列表如下: 函數名描述用例用例結果 Abs 返回絕對值 Abs(-1) 1M Acos 返回餘弦值對應的角度 Acos(1) 0d Asin - - d Atan - - d Ceiling 向上取整 Ceiling(1.5) 2d Cos - - d Exp 相 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...