在現代Web應用中集成 PDF.js (pdfjs-dist 5.2 ESM): 通過 jsdelivr 實現動態加載與批注功能的思考

PDF 文檔在現代 Web 應用中越來越常見,無論是作為文檔預覽、報告展示還是在線編輯的載體。Mozilla 的 PDF.js 是一個功能強大的 JavaScript 庫,它使得在瀏覽器端渲染和顯示 PDF 文件成為可能,無需依賴原生插件。

本文將深入探討如何在你的項目中使用 pdfjs-dist 庫的 5.2 版本,特別關注其通過 jsdelivr 引入的 ESM (ECMAScript Module) 版本 (.mjs 文件),并在此基礎上實現 PDF 文件的動態加載,同時對實現批注功能給出思路和指導。

為什么選擇 pdfjs-dist 5.2 ESM 和 jsdelivr?

  • pdfjs-dist: 這是 PDF.js 的發布版本,包含了核心渲染代碼和相關的構建產物,方便在項目中使用。
  • 5.2 版本: 選擇特定版本有助于保證代碼的穩定性,避免未來版本更新可能帶來的兼容性問題。
  • ESM (.mjs): ECMAScript Modules 是現代 JavaScript 的標準模塊系統。使用 ESM 可以更好地組織代碼、提高性能(如 tree shaking)并避免全局變量污染。它需要現代瀏覽器的支持,并通過 <script type="module"> 標簽引入。
  • jsdelivr: 這是一個免費的、快速的 CDN (Content Delivery Network),可以直接從 npm 包獲取文件。使用 CDN 可以加速庫的加載,減輕自己服務器的壓力。

第一步:核心集成 - 引入 pdfjs-dist ESM 版本

使用 ESM 格式引入庫需要在你的 HTML 文件中做一些調整。我們需要引入 pdf.min.mjs(核心庫)和 pdf.worker.min.mjs(用于在 Web Worker 中執行耗時任務)。

在你的 HTML 文件中,使用 <script type="module"> 標簽來編寫或引用你的 JavaScript 代碼:

<!DOCTYPE html>
<html>
<head><title>PDF.js ESM Integration</title><meta charset="UTF-8"><style>/* 可以添加一些基本的樣式 */#pdf-viewer-container {width: 800px; /* 根據需要設置容器寬度 */margin: 20px auto;border: 1px solid #ccc;overflow: auto; /* 如果PDF很大需要滾動 */}/* 其他樣式將在后續步驟中添加 */</style>
</head>
<body><h1>PDF.js ESM Example</h1><!-- 你的 PDF 查看器 UI 元素將在這里 --><!-- 使用 type="module" 引入你的主要 JavaScript 文件 --><!-- 假設你的主要邏輯在 main.js 中 --><script type="module" src="./main.js"></script></body>
</html>

在你的 main.js 文件中,使用 import 語句從 jsdelivr 引入 pdfjs-dist

// 從 jsdelivr 引入 pdfjs-dist 的核心模塊
// 使用具體的版本號 5.2.133 (這是一個示例版本號,請根據實際需要調整)
import { getDocument, GlobalWorkerOptions } from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.2.133/build/pdf.min.mjs';// 設置 workerSrc,這是 pdfjs-dist 必須的配置
// workerSrc 應該指向 pdf.worker.min.mjs 文件,且版本應與主庫一致
GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@5.2.133/build/pdf.worker.min.mjs';// 現在你可以使用導入的 getDocument 函數來加載 PDF
// 例如加載一個遠程 PDF 文件 (這將在下一節詳細展開)
/*
async function loadSamplePdf() {const samplePdfUrl = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf';try {const loadingTask = getDocument(samplePdfUrl);const pdfDocument = await loadingTask.promise;console.log('Sample PDF loaded:', pdfDocument);// TODO: 渲染 PDF 頁面} catch (error) {console.error('Error loading sample PDF:', error);}
}loadSamplePdf(); // 頁面加載后嘗試加載一個示例 PDF
*/

解釋:

  • <script type="module" src="./main.js">:告訴瀏覽器加載 ./main.js 文件作為一個 ES 模塊。
  • import { getDocument, GlobalWorkerOptions } from '...':使用 ESM 導入語法,從 jsdelivr 上的 pdf.min.mjs 導入 getDocument 函數和 GlobalWorkerOptions 對象。
  • GlobalWorkerOptions.workerSrc = '...'非常重要,這配置了 PDF.js worker 腳本的 URL。worker 負責在后臺處理 PDF 的解析,避免阻塞主線程,提高用戶體驗。

第二步:實現 PDF 文件的動態加載與渲染

在實際應用中,你通常需要根據用戶的操作(例如選擇文件或輸入 URL)來加載不同的 PDF 文件。我們需要一個 HTML 結構來接收用戶輸入,并編寫 JavaScript 代碼來處理加載和渲染過程。

擴展你的 HTML (在 <body> 內):

<!-- ... (head and previous body content) ... -->
<body><h1>My Dynamic PDF Viewer</h1><input type="file" id="pdfFilePicker" accept="application/pdf"><button id="loadPdfButton">Load Selected PDF</button><button id="loadUrlPdfButton">Load Sample PDF from URL</button><div id="pdf-viewer-container"><!-- PDF 頁面將渲染到這里 --></div><script type="module" src="./main.js"></script>
</body>
</html>

添加必要的 CSS (在 <style> 標簽內):

/* ... (previous styles) ... */
.pdfPage {margin-bottom: 10px; /* 頁與頁之間的間距 */border-bottom: 1px solid #eee; /* 頁之間分隔線 */position: relative; /* 為后續添加批注層做準備 */box-shadow: 0 0 8px rgba(0,0,0,0.1); /* 添加一些陰影效果 */
}
.pdfPage canvas {display: block; /* 防止 canvas 下方出現空白 */margin: 0 auto; /* canvas 居中 */
}
/* 批注層樣式,如果需要 */
.annotationLayer {position: absolute;top: 0;left: 0;width: 100%;height: 100%;pointer-events: none; /* 默認不捕獲鼠標事件,除非需要交互 */overflow: hidden; /* 避免批注超出頁面邊界 */
}

修改 main.js 來實現動態加載和渲染邏輯:

// ... (import and workerSrc setup) ...const pdfViewerContainer = document.getElementById('pdf-viewer-container');
const pdfFilePicker = document.getElementById('pdfFilePicker');
const loadPdfButton = document.getElementById('loadPdfButton');
const loadUrlPdfButton = document.getElementById('loadUrlPdfButton');let pdfDocument = null; // 存儲當前加載的 PDF 文檔對象// 函數:渲染單個頁面
async function renderPage(pageNum, pdfDocument) {if (!pdfDocument) return; // 確保文檔已加載try {const page = await pdfDocument.getPage(pageNum);const scale = 1.5; // 渲染比例,可以根據需要調整const viewport = page.getViewport({ scale: scale });// 創建一個 div 容器用于包裹 canvas 和其他層 (如批注層)const pageDiv = document.createElement('div');pageDiv.className = 'pdfPage';// 設置 pageDiv 的尺寸以匹配渲染后的頁面尺寸pageDiv.style.width = `${viewport.width}px`;pageDiv.style.height = `${viewport.height}px`;pageDiv.dataset.pageNumber = pageNum; // 存儲頁碼方便后續查找// 創建 canvas 元素用于渲染 PDF 內容const canvas = document.createElement('canvas');const context = canvas.getContext('2d');canvas.height = viewport.height;canvas.width = viewport.width;pageDiv.appendChild(canvas); // 將 canvas 添加到 pageDiv 中// 創建一個用于自定義批注的層 (將在第三步討論)const annotationLayer = document.createElement('div');annotationLayer.className = 'annotationLayer';// annotationLayer.style.width = `${viewport.width}px`; // 批注層尺寸通常與 viewport 一致// annotationLayer.style.height = `${viewport.height}px`;// 批注層需要定位在 pageDiv 內部,且覆蓋 canvas// 通過 CSS .annotationLayer 設置 absolute position 和 top/left 0 即可pageDiv.appendChild(annotationLayer); // 將批注層添加到 pageDiv 中pdfViewerContainer.appendChild(pageDiv); // 將 pageDiv 添加到主容器// 渲染 PDF 頁面內容到 canvasconst renderContext = {canvasContext: context,viewport: viewport,// 如果需要渲染內置的文本層或批注層,可以在這里指定容器// 引入并使用 TextLayerBuilder 和 AnnotationLayerBuilder 會增加代碼復雜度// textLayer: textLayerDiv,// annotationLayer: annotationLayerDiv,// annotationMode: pdfjsLib.AnnotationMode.ENABLE_FORMS,};// 執行渲染,這是一個異步操作await page.render(renderContext).promise;console.log(`Page ${pageNum} rendered.`);// TODO: 如果需要渲染內置文本層和批注層,在這里調用其 render 方法// 例如 textLayer.render(); annotationLayer.render();} catch (error) {console.error(`Error rendering page ${pageNum}:`, error);// 可以在頁面位置顯示一個錯誤消息}
}// 函數:加載并渲染整個 PDF
async function loadAndRenderPdf(pdfData) {// 清空之前的渲染內容pdfViewerContainer.innerHTML = '';pdfDocument = null; // 清除之前加載的文檔對象try {// 加載 PDF 文檔,pdfData 可以是 URL 字符串、ArrayBuffer、Blob 等const loadingTask = getDocument(pdfData);pdfDocument = await loadingTask.promise;console.log('PDF loaded:', pdfDocument);const numPages = pdfDocument.numPages;console.log('Number of pages:', numPages);// 循環渲染每一頁for (let pageNum = 1; pageNum <= numPages; pageNum++) {// 使用 Promise.resolve() 包裹 renderPage 可以讓循環繼續,而無需等待每頁渲染完成// 這樣可以更快地顯示第一頁Promise.resolve().then(() => renderPage(pageNum, pdfDocument));}// TODO: 如果需要處理 PDF 元數據、大綱、縮略圖等,可以在這里訪問 pdfDocument 對象} catch (reason) {console.error('Error during PDF loading:', reason);alert(`Failed to load PDF: ${reason.message || reason}`); // 給用戶提示}
}// 事件監聽器:加載本地文件
loadPdfButton.addEventListener('click', () => {const file = pdfFilePicker.files[0];if (file) {const reader = new FileReader();reader.onload = function(e) {const arrayBuffer = e.target.result;loadAndRenderPdf(arrayBuffer); // 加載 ArrayBuffer};reader.onerror = function(e) {console.error("FileReader error:", e);alert("Error reading file.");}reader.readAsArrayBuffer(file);} else {alert('Please select a PDF file.');}
});// 事件監聽器:加載示例 URL
loadUrlPdfButton.addEventListener('click', () => {const samplePdfUrl = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf'; // 替換為你自己的 PDF URLloadAndRenderPdf(samplePdfUrl); // 加載 URL
});// 注意:你的 HTML 文件需要通過 Web 服務器打開 (http:// 或 https://),
// 直接用瀏覽器打開本地文件 (file://) 可能因為跨域問題導致 worker 加載失敗或 PDF 文件無法加載。

解釋上述代碼:

  • #pdf-viewer-container:一個容器,用于容納所有渲染后的 PDF 頁面。
  • input[type="file"]button:用于觸發本地文件選擇和加載示例 URL。
  • pdfDocument: 存儲通過 getDocument 加載成功后的 PDF 文檔對象。
  • loadAndRenderPdf(pdfData):核心函數,接收 PDF 數據(可以是 URL 或 ArrayBuffer),清空容器,調用 getDocument 加載,然后遍歷所有頁碼,為每一頁調用 renderPage
  • renderPage(pageNum, pdfDocument):為指定的頁碼創建一個 div.pdfPage,內部包含一個 canvas 用于繪制 PDF 內容,以及一個 div.annotationLayer 用于后續的自定義批注。計算 viewport 并設置 canvas 尺寸,然后調用 page.render() 將頁面內容繪制到 canvas。
  • 事件監聽器:分別為文件選擇按鈕和加載 URL 按鈕添加點擊事件,讀取文件或指定 URL,然后調用 loadAndRenderPdf

至此,你已經構建了一個基本的 PDF 查看器,可以動態加載本地或遠程的 PDF 文件并將其渲染到頁面上。

第三步:實現自定義批注功能

重要提示: pdfjs-dist 庫的主要功能是渲染 PDF 內容,包括顯示 PDF 文件中已有的批注。它不提供添加、編輯或保存新的批注的功能。實現批注(如高亮、下劃線、矩形框、文本框等)是一個需要在 PDF 渲染層之上自定義構建的功能。

實現自定義批注功能的整體思路是在每個 PDF 頁面渲染出的 canvas 上方,疊加一個透明的 HTML 元素(我們在第二步中創建了 div.annotationLayer),然后在這個疊加層上通過 DOM 操作、SVG 繪制或額外的 Canvas 繪制來表示批注。

以下是實現批注功能的關鍵步驟和考慮因素:

  1. 批注層管理: 確保每個 PDF 頁面都有一個精確覆蓋其渲染區域的批注層 (div.annotationLayer)。通過 CSS position: absolute; top: 0; left: 0; width: 100%; height: 100%; 來定位。
  2. 用戶交互:
    • 工具選擇: 提供 UI 元素(按鈕、工具欄)讓用戶選擇要添加的批注類型(例如,高亮、矩形、文本框、直線等)。
    • 事件監聽: 在每個頁面的 annotationLayer 或一個委托的父容器上監聽鼠標事件 (mousedown, mousemove, mouseup) 或觸摸事件 (touchstart, touchmove, touchend)。
    • 繪制反饋:mousemove/touchmove 過程中,根據用戶選擇的工具和鼠標/觸摸位置,在批注層上實時繪制一個臨時圖形(例如,繪制矩形時顯示一個虛線框),給用戶即時反饋。
  3. 數據模型: 設計一個數據結構來存儲每個批注的信息。這些信息至少應該包括:
    • 批注所在的頁碼
    • 批注的類型(如 ‘highlight’, ‘rectangle’, ‘text’, ‘line’)。
    • 批注在頁面上的位置和尺寸信息。這通常需要將屏幕坐標轉換為 PDF 頁面內部的坐標系統。
    • 批注的樣式信息(顏色、線寬、透明度等)。
    • 如果是文本批注,則需要存儲文本內容
  4. 坐標轉換: 這是實現批注的關鍵難點之一。鼠標/觸摸事件提供的坐標是相對于瀏覽器視口或頁面的像素坐標。你需要將這些像素坐標轉換為 PDF 頁面內部的坐標(PDF 坐標系統通常以點為單位,原點在左下角)。pdfjs-dist 提供的 page.getViewport(scale).convertToPdfPoint(x, y) 方法可以將視口像素坐標轉換為 PDF 坐標,而 page.getViewport(scale).convertToViewportPoint(x, y) 可以將 PDF 坐標轉換為視口像素坐標。在處理不同縮放比例時,正確進行坐標轉換至關重要。
  5. 批注渲染: 當頁面加載、縮放或批注數據更新時,遍歷當前頁面的批注數據。根據批注類型,在對應的 annotationLayer 中創建并添加相應的 HTML 元素、SVG 元素或在批注層的 Canvas 上繪制圖形來顯示批注。例如:
    • 高亮:創建 <span><div> 元素,設置背景顏色和位置。
    • 矩形/直線:創建 SVG 元素 (<rect>, <line>) 并設置屬性,或者在批注層的 Canvas 上使用 2D Context 繪制。
    • 文本框:創建 <div><textarea>,設置位置和內容。
  6. 批注管理界面: 實現選中批注、顯示編輯框、拖動、改變大小、刪除等功能。這涉及到監聽批注元素的事件,更新批注數據,并重新渲染批注層。
  7. 數據持久化: 實現將批注數據保存到后端服務器或瀏覽器的本地存儲中,以便下次打開同一個 PDF 時可以加載并恢復批注。批注數據通常需要與 PDF 文件本身關聯(例如通過 PDF 的哈希值或文件名)。

關于改造官方 viewer.html

pdfjs-dist 源碼中的 web/viewer.htmlweb/viewer.js 提供了一個完整的 PDF 查看器實現。雖然你可以借鑒其結構和邏輯(尤其是文本層和內置批注層的渲染方式),但直接修改和嵌入到你的項目會非常復雜。viewer.js 是為一個獨立應用設計的,其內部耦合度高,依賴于許多輔助類和資源。從頭開始,使用核心庫構建你自己的查看器,并逐步添加所需功能(包括批注),通常是更靈活和易于維護的方式。

總結

通過 jsdelivr 引入 pdfjs-dist 的 5.2 版本 ESM 文件,你可以輕松地在現代 Web 應用中集成 PDF 查看功能。使用 <script type="module">import 是 ESM 的標準方式。實現 PDF 的動態加載需要處理文件讀取或 URL 請求,并通過 getDocumentrenderPage 函數來完成。

然而,實現自定義的 PDF 批注功能是一個相對獨立的任務,它建立在 PDF 渲染之上,需要你自行設計批注的數據模型、用戶交互、坐標轉換以及批注的渲染和管理邏輯。這部分功能需要投入額外的開發工作,并且可能需要處理復雜的細節,尤其是在保證批注位置準確性和在不同縮放級別下同步更新方面。如果你需要開箱即用的復雜批注功能,可能需要考慮集成商業的 PDF SDK。

希望這篇博文能幫助你理解如何在現代 Web 項目中集成和使用 pdfjs-dist,并為實現動態加載和批注功能提供清晰的思路。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/80248.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/80248.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/80248.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于FPGA控制ADC0832雙通道采樣+電壓電流采樣+LCD屏幕顯示

基于FPGA控制ADC0832雙通道采樣電壓電流采樣LCD屏幕顯示 前言一、芯片手冊閱讀1.SPI通信時序 二、仿真分析三、代碼分析總結視頻演示 前言 定制 要求使用ADC0832芯片進行ADC采樣。其中電壓采樣以及電流采樣是固定電路&#xff0c;是硬件設計&#xff0c;跟軟件沒沒關系。本質上…

生產部署方案pm2配合python3腳本

前言 使用python3來處理redis 消息隊列&#xff0c;記錄下生產部署方案 「生產部署方案」&#xff1a; 多進程&#xff08;動態擴容&#xff09;無限自愈日志自動壓縮系統級守護可多隊列多worker 終極穩健版&#xff1a;PM2 Logrotate 自動擴容 守護鏈 適合&#xff1a…

Python全流程開發實戰:基于IMAP協議安全下載個人Gmail郵箱內所有PDF附件

文章目錄 一、需求分析與安全前置&#xff1a;為什么需要專用工具&#xff1f;1.1 痛點場景1.2 技術方案選擇 二、準備工作&#xff1a;Gmail賬號安全配置與環境搭建2.1 開啟兩步驗證&#xff08;必做&#xff01;&#xff09;2.2 創建應用專用密碼&#xff08;替代普通密碼&am…

巧用python之--模仿PLC(PLC模擬器)

工作中用到了VM(VisionMaster4.3)有時候需要和PLC打交道,但是PLC畢竟是別人的,不方便修改別人的程序,這時候需要一個靈活的PLC模擬器是多么好呀! 先說背景: PLC型號 匯川Easy521: Modbus TCP 192.168.1.10:502 在匯川Easy521中Modbus保持寄存器D寄存器 ,在modbus協議中 0-4區…

docker構建鏡像并上傳dockerhub

docker構建鏡像并上傳dockerhub 前提條件&#xff1a;需要連接梯子 將梯子配置到虛擬機中&#xff08;確保主機能夠連接 hub.docker.com&#xff09; 使用ipconfig 查詢主機的 ip4地址虛擬機的連接模式改成橋接模式&#xff08;復制主機的地址網絡&#xff09;將ip4配置到虛擬…

python實現的音樂播放器

python實現的音樂播放器 音樂播放器,原來寫過一個簡陋的例子,可見 https://blog.csdn.net/cnds123/article/details/137874107 那個不能拖動播放進度條上的滑塊到新的位置播放。下面介紹的可以拖動播放進度條上的滑塊到新的位置播放。 簡單實用的音樂播放器 這個簡單實用的…

[網安工具] 端口信息收集工具 —— 御劍高速 TCP 全端口掃描工具 · 使用手冊

&#x1f31f;想了解其它網安工具&#xff1f;看看這個&#xff1a;[網安工具] 網絡安全工具管理 —— 工具倉庫 管理手冊 https://github.com/NepoloHebo/Yujian-high-speed-TCP-full-port-scannerhttps://github.com/NepoloHebo/Yujian-high-speed-TCP-full-port-scanner 0…

數字孿生賦能智慧城市:從概念到落地的深度實踐

在城市規模與復雜度持續攀升的當下&#xff0c;傳統管理模式已難以滿足現代城市精細化治理需求。數字孿生技術憑借構建虛擬城市鏡像、實現實時數據交互與智能決策的特性&#xff0c;成為智慧城市建設的核心引擎。本文將通過多個典型案例&#xff0c;深度解析數字孿生技術如何重…

DeFi開發系統軟件開發:技術架構與生態重構

DeFi開發系統軟件開發&#xff1a;技術架構與生態重構 ——2025年去中心化金融開發的范式革新與實踐指南 一、技術架構演進&#xff1a;從單一鏈到多鏈混合引擎 現代DeFi系統開發已從單一公鏈架構轉向“跨鏈互操作混合模式”&#xff0c;結合中心化效率與去中心化安全雙重優勢…

相同IP和端口的服務器ssh連接時出現異常

起因 把服務器上的一個虛擬機搞壞了&#xff0c;所以刪除重新創建了一個&#xff0c;端口號和IP與之前的虛擬機相同。 ssh usernameIP -p port 時報錯 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone…

驗證es啟動成功

1. 查看命令行輸出信息 在啟動 Elasticsearch 時&#xff0c;命令行窗口會輸出一系列日志信息。若啟動成功&#xff0c;日志里通常會有類似下面的信息&#xff1a; plaintext [2025-05-06T13:20:00,000][INFO ][o.e.n.Node ] [node_name] started其中 [node_na…

CentOS網絡之network和NetworkManager深度解析

文章目錄 CentOS網絡之network和NetworkManager深度解析1. CentOS網絡服務發展歷史1.1 傳統network階段&#xff08;CentOS 5-6&#xff09;1.2 過渡期&#xff08;CentOS 7&#xff09;1.3 新時代&#xff08;CentOS 8&#xff09; 2. network和NetworkManager的核心區別3. ne…

Unity:父掛 Rigidbody2D、子掛 Collider2D 時觸發器不生效的問題分析

目錄 ?問題現象 &#x1f50d; 排查與定位 ?? Unity 觸發機制的核心要求 ? 為什么把 Collider2D 移到父物體后就能觸發&#xff1f; &#x1f4a1; 解決方案 在 Unity 2D 游戲開發中&#xff0c;很多人習慣用父物體掛載 Rigidbody2D&#xff0c;而將不同的身體部位&am…

Google AI版圖:解析AI Studio, Gemini, NotebookLM與GCP

1. 2C vs 2B: AI Studio: 主要是面向開發者&#xff0c;提供一個易用的界面來探索和構建基于Google模型的應用。雖然最終的應用可能服務于C端或B端&#xff0c;但AI Studio本身更多是一個開發者的工具平臺&#xff0c;可以看作是連接模型能力和各種應用的橋梁。它可以被個人開…

Oracle EBS AP發票被預付款核算創建會計科目時間超長

背景 由于客戶職能部門的水電、通信和物業等等費用統一管理或對接部門報銷費,在報銷費的時候,用戶把所有費用分攤到各個末級部門,形成AP發票行有上千行, 問題癥狀 1、用戶過賬時,請求創建會計科目一直執行20多個小時未完成,只能手工強行取消請求。 2、取消請求以后,從后…

MySQL中MVCC指什么?

簡要回答&#xff1a; MVCC&#xff08;multi version concurrency control&#xff09;即多版本并發控制&#xff0c;為了確保多線程下數據的安全&#xff0c;可以通過undo log和ReadView來實現不同的事務隔離級別。 對于已提交讀和可重復讀隔離級別的事務來說&#xff0c;M…

賽季7靶場 -- Checker --User flag

本系列僅說明靶場的攻擊思路&#xff0c;不會給出任何的詳細代碼執行步驟&#xff0c;因為個人覺得找到合適的工具以實現攻擊思路的能力也非常重要。root要逆向&#xff0c;沒做了&#xff0c;但是user flag也有借鑒意義&#xff0c;關于2FA的繞過我們有必要了解 1.首先Nmap掃描…

【RAG技術全景解讀】從原理到工業級應用實踐

目錄 &#x1f31f; 前言&#x1f3d7;? 技術背景與價值&#x1f6a8; 當前技術痛點&#x1f6e0;? 解決方案概述&#x1f465; 目標讀者說明 &#x1f50d; 一、技術原理剖析&#x1f4d0; 核心概念圖解&#x1f4a1; 核心作用講解?? 關鍵技術模塊說明?? 技術選型對比 &…

【嵌入式開發-RS-485】

嵌入式開發-RS-485 ■ RS-485 連接方式■ RS-485 半雙工通訊■ RS-485 的特點■ UART硬流控■ RS-4851. 全雙工、半雙工接線2. 拓撲結構3. RS-485收發器3.1 發送模式&#xff08;TX&#xff09;3.2 接收模式&#xff08;RX&#xff09; 4. RS-485數據鏈路5. RS-485常用電路6. C…

[硬件電路-18]:MCU - LPC1765FBD100是恩智浦(NXP)半導體推出的一款基于ARM Cortex-M3內核的高性能32位微控制器

LPC1765FBD100是恩智浦&#xff08;NXP&#xff09;半導體推出的一款基于ARM Cortex-M3內核的高性能32位微控制器&#xff0c;具備高集成度、低功耗、豐富的外設接口和強大的處理能力&#xff0c;適用于工業控制、消費電子、醫療設備、通信系統等嵌入式應用場景。 以下從核心特…