本教程詳細(xì)介紹了如何利用JavaScript庫(kù)html2pdf在客戶端生成PDF文檔,并將其以Base64編碼字符串的形式通過(guò)ajax異步發(fā)送至服務(wù)器。在服務(wù)器端,我們將使用php處理接收到的Base64數(shù)據(jù),去除URI前綴后進(jìn)行解碼,最終通過(guò)PHPMailer庫(kù)將生成的PDF作為附件發(fā)送電子郵件。本文將涵蓋從前端PDF生成、數(shù)據(jù)傳輸?shù)胶蠖颂幚砗袜]件發(fā)送的全過(guò)程,并提供完整的代碼示例和注意事項(xiàng)。
1. 概述與準(zhǔn)備工作
在web應(yīng)用中,有時(shí)我們需要將用戶界面上的內(nèi)容轉(zhuǎn)換為pdf文檔,并將其通過(guò)郵件發(fā)送。直接在客戶端生成并發(fā)送郵件是不安全的,也無(wú)法實(shí)現(xiàn)。因此,常見(jiàn)的做法是:在客戶端生成pdf的二進(jìn)制數(shù)據(jù)(通常是base64編碼),通過(guò)ajax發(fā)送到服務(wù)器,再由服務(wù)器端腳本(如php)處理并發(fā)送郵件。
本教程將使用以下關(guān)鍵技術(shù)棧:
- 前端: html2pdf.JS (基于html2canvas和jsPDF) 用于將HTML內(nèi)容轉(zhuǎn)換為PDF,jquery 或原生 XMLhttpRequest 進(jìn)行Ajax通信。
- 后端: PHP 處理數(shù)據(jù),PHPMailer 庫(kù)發(fā)送電子郵件。
在開始之前,請(qǐng)確保你的項(xiàng)目已引入 html2pdf.js 和 jQuery (如果使用),并且服務(wù)器端已安裝 PHPmailer。
2. 客戶端:生成PDF并以Base64字符串形式傳輸
在客戶端,我們首先使用html2pdf.js將指定的html元素內(nèi)容轉(zhuǎn)換為PDF。關(guān)鍵在于不直接保存PDF,而是將其輸出為Base64編碼的URI字符串。
2.1 HTML結(jié)構(gòu)準(zhǔn)備
確保你的HTML頁(yè)面中有一個(gè)包含需要轉(zhuǎn)換為PDF內(nèi)容的元素,例如:
立即學(xué)習(xí)“PHP免費(fèi)學(xué)習(xí)筆記(深入)”;
<div id="printPage"> <h1>這是一個(gè)PDF標(biāo)題</h1> <p>這是需要轉(zhuǎn)換為PDF的內(nèi)容。</p> <ul> <li>列表項(xiàng)1</li> <li>列表項(xiàng)2</li> </ul> @@##@@ </div> <button id="sendPdfBtn">生成并發(fā)送PDF</button>
2.2 JavaScript代碼:生成Base64 PDF并發(fā)送Ajax請(qǐng)求
html2pdf.js 提供了一個(gè) output() 方法,可以指定輸出格式。我們使用 datauristring 格式來(lái)獲取PDF的Base64編碼字符串。
// 獲取需要轉(zhuǎn)換為PDF的HTML元素 let page = document.getElementById('printPage'); // html2pdf 配置選項(xiàng) var pdfOptions = { margin: [5, 0, 0, 0], // 上右下左邊距 filename: 'document.pdf', // 盡管不直接保存,但此文件名會(huì)作為附件默認(rèn)名 image: { type: 'jpeg', quality: 1 // 圖片質(zhì)量 }, pagebreak: { mode: ['legacy'] // 分頁(yè)模式 }, html2canvas: { scale: 3 // html2canvas 渲染比例 }, jsPDF: { unit: 'mm', // 單位 format: 'a4', // 紙張格式 orientation: 'portrait' // 方向:縱向 } }; // 監(jiān)聽(tīng)按鈕點(diǎn)擊事件 $(document).on('click', '#sendPdfBtn', async function() { let pdfContent; try { // 使用 await 等待 PDF 生成為 datauristring pdfContent = await html2pdf().from(page).set(pdfOptions).outputPdf('datauristring'); // 或者使用 .then() 回調(diào)方式 // await html2pdf().from(page).set(pdfOptions).outputPdf('datauristring').then(function(pdfAsString) { // pdfContent = pdfAsString; // }); // 準(zhǔn)備 Ajax 請(qǐng)求數(shù)據(jù) let ajaxUrl = 'your_php_mailer_script.php'; // 替換為你的php腳本URL let transaction = 'someTransactionType'; // 示例數(shù)據(jù) let transactionId = '12345'; // 示例數(shù)據(jù) $.ajax({ type: "POST", url: ajaxUrl, data: { action: "sendEmail", // 示例動(dòng)作 transaction: transaction, transactionId: transactionId, emailTo: $("#emailTo").val() || "recipient@example.com", // 接收者郵箱 emailCc: $("#emailCc").val(), // 抄送 emailBcc: $("#emailBcc").val(), // 密送 emailSubject: $("#emailSubject").val() || "來(lái)自網(wǎng)站的PDF報(bào)告", // 郵件主題 emailMessage: $("#emailMessage").val() || "請(qǐng)查收附件中的PDF文檔。", // 郵件內(nèi)容 pdfContent: pdfContent // 核心:Base64編碼的PDF內(nèi)容 }, success: function(data) { console.log("郵件發(fā)送響應(yīng):", data); alert("郵件發(fā)送成功!"); }, error: function(xhr, status, error) { console.error("Ajax請(qǐng)求失敗:", status, error); alert("郵件發(fā)送失敗,請(qǐng)查看控制臺(tái)了解詳情。"); } }); } catch (error) { console.error("PDF生成或發(fā)送過(guò)程中發(fā)生錯(cuò)誤:", error); alert("PDF生成或發(fā)送失敗。"); } });
代碼解釋:
- html2pdf().from(page).set(pdfOptions).outputPdf(‘datauristring’): 這是核心部分。它指示 html2pdf 從 page 元素生成PDF,應(yīng)用 pdfOptions 配置,并將結(jié)果輸出為 datauristring。
- datauristring 格式通常是 data:application/pdf;base64,JVBERi…,其中 JVBERi… 是PDF的Base64編碼內(nèi)容。服務(wù)器端需要處理這個(gè)前綴。
- await 關(guān)鍵字用于等待異步的 html2pdf 操作完成。為了使用 await,你的函數(shù)需要被聲明為 async。
- $.ajax() 用于發(fā)送POST請(qǐng)求,將 pdfContent 作為數(shù)據(jù)的一部分發(fā)送到服務(wù)器。
3. 服務(wù)器端:處理Base64 PDF數(shù)據(jù)并使用PHPMailer發(fā)送郵件
在服務(wù)器端(PHP腳本),我們將接收前端發(fā)送過(guò)來(lái)的Base64字符串,進(jìn)行解碼,然后使用PHPMailer將其作為附件發(fā)送。
3.1 PHP代碼:接收、解碼并發(fā)送郵件
<?php // 引入 PHPMailer 相關(guān)的類文件 use PHPMailerPHPMailerPHPMailer; use PHPMailerPHPMailerException; require 'path/to/PHPMailer/src/Exception.php'; require 'path/to/PHPMailer/src/PHPMailer.php'; require 'path/to/PHPMailer/src/SMTP.php'; // 設(shè)置響應(yīng)頭,防止CORS問(wèn)題或確保JSON響應(yīng) header('Content-Type: application/json'); // 檢查是否是POST請(qǐng)求 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid request method.']); exit; } // 獲取前端發(fā)送的PDF內(nèi)容及其他郵件信息 $pdfdoc = $_POST['pdfContent'] ?? ''; $emailTo = $_POST['emailTo'] ?? ''; $emailCc = $_POST['emailCc'] ?? ''; $emailBcc = $_POST['emailBcc'] ?? ''; $emailSubject = $_POST['emailSubject'] ?? '郵件主題未設(shè)置'; $emailMessage = $_POST['emailMessage'] ?? '郵件內(nèi)容為空'; // 驗(yàn)證PDF內(nèi)容是否存在 if (empty($pdfdoc)) { echo json_encode(['status' => 'error', 'message' => 'PDF content is missing.']); exit; } // 提取Base64編碼的PDF數(shù)據(jù) // 'data:application/pdf;base64,' 這個(gè)前綴需要被移除 $pdfData = substr($pdfdoc, strpos($pdfdoc, ',') + 1); // 從逗號(hào)后面開始截取 // 對(duì)Base64數(shù)據(jù)進(jìn)行解碼 $decodedPdf = base64_decode($pdfData); // 檢查解碼是否成功 if ($decodedPdf === false) { echo json_encode(['status' => 'error', 'message' => 'Failed to decode PDF content.']); exit; } // PHPMailer 實(shí)例 $mail = new PHPMailer(true); // true enables exceptions try { // 服務(wù)器設(shè)置 (根據(jù)你的SMTP服務(wù)商配置) $mail->SMTPDebug = 0; // 0 = off (for production), 1 = client messages, 2 = client and server messages $mail->isSMTP(); // 使用SMTP $mail->Host = 'smtp.yourdomain.com'; // SMTP 服務(wù)器地址 $mail->SMTPAuth = true; // 啟用SMTP認(rèn)證 $mail->Username = 'your_email@yourdomain.com'; // SMTP 用戶名 $mail->Password = 'your_email_password'; // SMTP 密碼 $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // 啟用TLS加密,或者 PHPMailer::ENCRYPTION_SMTPS for SSL $mail->Port = 587; // TLS 端口通常是 587,SSL 端口通常是 465 // 收件人 $mail->setFrom('sender@yourdomain.com', 'Your Company Name'); // 發(fā)件人郵箱和名稱 $mail->addAddress($emailTo); // 收件人郵箱 if (!empty($emailCc)) { $mail->addCC($emailCc); // 抄送 } if (!empty($emailBcc)) { $mail->addBCC($emailBcc); // 密送 } // 附件 // AddStringAttachment(string $string, string $filename, string $encoding = 'base64', string $type = '', string $disposition = 'attachment') // 這里我們傳入的是原始的二進(jìn)制PDF數(shù)據(jù),所以編碼類型是 'base64' (因?yàn)樗笆莃ase64編碼的),MIME類型是 'application/pdf' $mail->AddStringAttachment($decodedPdf, "GeneratedDocument.pdf", "base64", "application/pdf"); // 內(nèi)容 $mail->isHTML(true); // 郵件內(nèi)容為HTML格式 $mail->Subject = $emailSubject; // 郵件主題 $mail->Body = nl2br(htmlspecialchars($emailMessage)); // 郵件HTML內(nèi)容,nl2br保留換行,htmlspecialchars防止xss $mail->AltBody = strip_tags($emailMessage); // 純文本內(nèi)容,用于不支持HTML的郵件客戶端 $mail->send(); echo json_encode(['status' => 'success', 'message' => 'Message has been sent.']); } catch (Exception $e) { echo json_encode(['status' => 'error', 'message' => "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"]); } ?>
代碼解釋:
- PHPMailer 引入: 確保 require 語(yǔ)句指向你的PHPMailer庫(kù)的正確路徑。
- 數(shù)據(jù)提取: $_POST[‘pdfContent’] 獲取前端發(fā)送的Base64字符串。
- substr($pdfdoc, strpos($pdfdoc, ‘,’) + 1): 這是關(guān)鍵一步。datauristring 格式包含一個(gè)前綴(例如 data:application/pdf;base64,),我們需要用 strpos 找到逗號(hào)的位置,然后用 substr 從逗號(hào)之后開始截取,從而得到純粹的Base64編碼數(shù)據(jù)。
- base64_decode($pdfData): 將純粹的Base64編碼數(shù)據(jù)解碼回原始的二進(jìn)制PDF數(shù)據(jù)。
- $mail->AddStringAttachment(…): 這是PHPMailer中用于添加字符串作為附件的方法。
- 第一個(gè)參數(shù) $decodedPdf 是解碼后的二進(jìn)制PDF數(shù)據(jù)。
- 第二個(gè)參數(shù) “GeneratedDocument.pdf” 是附件的文件名。
- 第三個(gè)參數(shù) “base64” 表示附件的編碼方式(盡管我們傳入的是解碼后的數(shù)據(jù),PHPMailer內(nèi)部會(huì)根據(jù)這個(gè)參數(shù)進(jìn)行處理,這里指明原始數(shù)據(jù)是Base64編碼的)。
- 第四個(gè)參數(shù) “application/pdf” 是附件的MIME類型,這非常重要,它告訴郵件客戶端這是一個(gè)PDF文件。
- SMTP 配置: 替換 host, username, password, port, SMTPSecure 為你的SMTP服務(wù)器的實(shí)際配置。
- 錯(cuò)誤處理: try…catch 塊用于捕獲PHPMailer可能拋出的異常,并返回詳細(xì)的錯(cuò)誤信息。
4. 注意事項(xiàng)與總結(jié)
- 文件大小限制: 通過(guò)Ajax發(fā)送Base64編碼的數(shù)據(jù)會(huì)顯著增加數(shù)據(jù)量(Base64編碼會(huì)使數(shù)據(jù)量增大約33%)。對(duì)于非常大的PDF文件,這可能會(huì)導(dǎo)致HTTP請(qǐng)求體過(guò)大,超出服務(wù)器或Web服務(wù)器(如nginx, apache)的請(qǐng)求體大小限制。你可能需要調(diào)整服務(wù)器配置(例如PHP的 post_max_size 和 upload_max_filesize,以及Web服務(wù)器的 client_max_body_size)。
- 安全性:
- 始終在服務(wù)器端驗(yàn)證所有接收到的輸入數(shù)據(jù),包括郵件地址、主題和內(nèi)容,以防止注入攻擊(如XSS)。
- 不要直接暴露你的SMTP憑據(jù)在客戶端代碼中。PHPMailer配置應(yīng)完全在服務(wù)器端進(jìn)行。
- 考慮對(duì)生成的PDF內(nèi)容進(jìn)行服務(wù)器端驗(yàn)證或消毒,如果內(nèi)容來(lái)源于用戶輸入。
- 異步操作: html2pdf 的生成過(guò)程是異步的。確保你在發(fā)送Ajax請(qǐng)求之前,PDF內(nèi)容已經(jīng)完全生成并賦值給 pdfContent 變量。使用 await 或 .then() 回調(diào)是處理異步操作的正確方式。
- MIME 類型: 在 AddStringAttachment 中正確指定 application/pdf MIME 類型至關(guān)重要,它能確保郵件客戶端正確識(shí)別并顯示附件。
- 調(diào)試: 在開發(fā)階段,將 PHPMailer 的 SMTPDebug 設(shè)置為 1 或 2 可以幫助你查看SMTP通信過(guò)程,從而診斷連接或認(rèn)證問(wèn)題。在生產(chǎn)環(huán)境中,務(wù)必將其設(shè)置為 0。
- PHPMailer 路徑: 確保 require 語(yǔ)句中PHPMailer庫(kù)的路徑是正確的。
通過(guò)以上步驟,你就可以成功地在客戶端生成PDF,并通過(guò)Ajax將其發(fā)送到服務(wù)器,再由PHPMailer將其作為附件發(fā)送電子郵件。這種方法兼顧了客戶端生成PDF的靈活性和服務(wù)器端發(fā)送郵件的可靠性與安全性。