在现代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 绘制来表示批注。
以下是实现批注功能的关键步骤和考虑因素:
- 批注层管理: 确保每个 PDF 页面都有一个精确覆盖其渲染区域的批注层 (
div.annotationLayer
)。通过 CSSposition: absolute; top: 0; left: 0; width: 100%; height: 100%;
来定位。 - 用户交互:
- 工具选择: 提供 UI 元素(按钮、工具栏)让用户选择要添加的批注类型(例如,高亮、矩形、文本框、直线等)。
- 事件监听: 在每个页面的
annotationLayer
或一个委托的父容器上监听鼠标事件 (mousedown
,mousemove
,mouseup
) 或触摸事件 (touchstart
,touchmove
,touchend
)。 - 绘制反馈: 在
mousemove
/touchmove
过程中,根据用户选择的工具和鼠标/触摸位置,在批注层上实时绘制一个临时图形(例如,绘制矩形时显示一个虚线框),给用户即时反馈。
- 数据模型: 设计一个数据结构来存储每个批注的信息。这些信息至少应该包括:
- 批注所在的页码。
- 批注的类型(如 ‘highlight’, ‘rectangle’, ‘text’, ‘line’)。
- 批注在页面上的位置和尺寸信息。这通常需要将屏幕坐标转换为 PDF 页面内部的坐标系统。
- 批注的样式信息(颜色、线宽、透明度等)。
- 如果是文本批注,则需要存储文本内容。
- 坐标转换: 这是实现批注的关键难点之一。鼠标/触摸事件提供的坐标是相对于浏览器视口或页面的像素坐标。你需要将这些像素坐标转换为 PDF 页面内部的坐标(PDF 坐标系统通常以点为单位,原点在左下角)。
pdfjs-dist
提供的page.getViewport(scale).convertToPdfPoint(x, y)
方法可以将视口像素坐标转换为 PDF 坐标,而page.getViewport(scale).convertToViewportPoint(x, y)
可以将 PDF 坐标转换为视口像素坐标。在处理不同缩放比例时,正确进行坐标转换至关重要。 - 批注渲染: 当页面加载、缩放或批注数据更新时,遍历当前页面的批注数据。根据批注类型,在对应的
annotationLayer
中创建并添加相应的 HTML 元素、SVG 元素或在批注层的 Canvas 上绘制图形来显示批注。例如:- 高亮:创建
<span>
或<div>
元素,设置背景颜色和位置。 - 矩形/直线:创建 SVG 元素 (
<rect>
,<line>
) 并设置属性,或者在批注层的 Canvas 上使用 2D Context 绘制。 - 文本框:创建
<div>
或<textarea>
,设置位置和内容。
- 高亮:创建
- 批注管理界面: 实现选中批注、显示编辑框、拖动、改变大小、删除等功能。这涉及到监听批注元素的事件,更新批注数据,并重新渲染批注层。
- 数据持久化: 实现将批注数据保存到后端服务器或浏览器的本地存储中,以便下次打开同一个 PDF 时可以加载并恢复批注。批注数据通常需要与 PDF 文件本身关联(例如通过 PDF 的哈希值或文件名)。
关于改造官方 viewer.html
:
pdfjs-dist
源码中的 web/viewer.html
和 web/viewer.js
提供了一个完整的 PDF 查看器实现。虽然你可以借鉴其结构和逻辑(尤其是文本层和内置批注层的渲染方式),但直接修改和嵌入到你的项目会非常复杂。viewer.js
是为一个独立应用设计的,其内部耦合度高,依赖于许多辅助类和资源。从头开始,使用核心库构建你自己的查看器,并逐步添加所需功能(包括批注),通常是更灵活和易于维护的方式。
总结
通过 jsdelivr 引入 pdfjs-dist
的 5.2 版本 ESM 文件,你可以轻松地在现代 Web 应用中集成 PDF 查看功能。使用 <script type="module">
和 import
是 ESM 的标准方式。实现 PDF 的动态加载需要处理文件读取或 URL 请求,并通过 getDocument
和 renderPage
函数来完成。
然而,实现自定义的 PDF 批注功能是一个相对独立的任务,它建立在 PDF 渲染之上,需要你自行设计批注的数据模型、用户交互、坐标转换以及批注的渲染和管理逻辑。这部分功能需要投入额外的开发工作,并且可能需要处理复杂的细节,尤其是在保证批注位置准确性和在不同缩放级别下同步更新方面。如果你需要开箱即用的复杂批注功能,可能需要考虑集成商业的 PDF SDK。
希望这篇博文能帮助你理解如何在现代 Web 项目中集成和使用 pdfjs-dist
,并为实现动态加载和批注功能提供清晰的思路。
相关文章:
在现代Web应用中集成 PDF.js (pdfjs-dist 5.2 ESM): 通过 jsdelivr 实现动态加载与批注功能的思考
PDF 文档在现代 Web 应用中越来越常见,无论是作为文档预览、报告展示还是在线编辑的载体。Mozilla 的 PDF.js 是一个功能强大的 JavaScript 库,它使得在浏览器端渲染和显示 PDF 文件成为可能,无需依赖原生插件。 本文将深入探讨如何在你的项…...

C++STL——priority_queue
优先队列 前言优先队列仿函数头文件 前言 本篇主要讲解优先队列及其底层实现。 优先队列 优先队列的本质就是个堆,其与queue一样,都是容器适配器,不过优先队列是默认为vector实现的。priority_queue的接口优先队列默认为大根堆。 仿函数 …...
没有 Mac,如何把 iOS App 成功上架?
开发者的 iOS 上架折腾记:没有 Mac,也能搞定? 最近在帮朋友把一个跨平台 Flutter 项目上架到 App Store,结果被 iOS 上架的那套流程卡得头都大了。其实这也不是第一次碰壁了——每次到“申请证书 打包 上传”的时候,…...
C++ Vector深度易错点指南(临时抱佛脚)(基础用法;进阶;高级;实战)
Vector 1. Vector 基础概念1.1 内存结构1.2 初始化方法2.1 访问方式对比2.2 遍历流程3.1 扩容流程3.2 迭代器失效3.2.1 插入操作导致迭代器失效3.2.2 删除操作导致迭代器失效3.2.3 避免迭代器失效的建议4.1 排序与去重排序去重性能分析4.2 查找与条件删除查找条件删除性能分析5…...

深入解析WPF中的3D图形编程:材质与光照
引言 在Windows Presentation Foundation (WPF) 中创建三维(3D)图形是一项既有趣又具有挑战性的任务。为了帮助开发者更好地理解如何使用WPF进行3D图形的渲染,本文将深入探讨GeometryModel3D类及其相关的材质和光源设置。 1、GeometryModel3D类简介 GeometryMode…...

SolidWork-2023 鼠標工程
地址 https://github.com/MartinxMax/SW2023-Project/tree/main/mouse 鼠標...

vscode预览模式(点击文件时默认覆盖当前标签,标签名称显示为斜体,可通过双击该标签取消)覆盖标签、新窗打开
文章目录 VS Code 预览模式如何取消预览模式(即“固定”标签页)?预览模式有什么用? VS Code 预览模式 在 VS Code 中,当你单击文件浏览器(例如,资源管理器侧边栏)中的某个文件时&am…...

记录踩过的坑-金蝶云苍穹平台-轻分析和轻报表(慢慢更新)
未发现AppIdName(qing rpt)服务或访问服务网络异常 前提是有许可和权限。 去console(云基础平台控制台),点击服务管理,编辑mservice-更新升级-环境变量,在appIds里增加qing_rpt 查看数据库 如果是采用公共数据源连接…...

每日一题洛谷T534125 合数c++
字符串输入,看所有位数加起来的数是不是3的倍数 是,直接输出,不是,删除1或2 特判全是1和全是2的情况 直接检测末尾数字可以特判2 特判1时,还要特判11和111,其他数字,k是奇数时是质数&#x…...

数据链共享:从印巴空战到工业控制的跨越性应用
摘要 本文通过对印巴空战中数据链共享发挥关键作用的分析,引出数据链共享在工业控制领域同样具有重大价值的观点。深入阐述 DIOS 工业控制操作系统作为工业数据链共享基础技术的特点、架构及应用优势,对比空战场景与工业控制场景下数据链共享的相…...
Go多服务项目结构优化:为何每个服务单独设置internal目录?
文章目录 Go多服务项目结构优化:为何每个服务单独设置internal目录?背景什么是 Go 的 internal 机制?传统根 internal 目录的局限为什么要每个服务单独设置 internal ?推荐结构示例 总结 Go多服务项目结构优化:为何每个…...

图解gpt之Seq2Seq架构与序列到序列模型
今天深入探讨如何构建更强大的序列到序列模型,特别是Seq2Seq架构。序列到序列模型,顾名思义,它的核心任务就是将一个序列映射到另一个序列。这个序列可以是文本,也可以是其他符号序列。最早,人们尝试用一个单一的RNN来…...

Linux--JsonCpp
1.JsonCpp 简介 JsonCpp 是一个用于 C 的 JSON 解析和生成库,支持 JSON 数据的读写、解析和序列化。它提供了简单的 API 来操作 JSON 对象、数组、字符串、数字等类型,是 C 开发中处理 JSON 数据的常用工具。 核心功能与类 JsonCpp 主要包含以下核心类…...
程序代码篇---Python视频流
文章目录 前言一、OpenCV 视频流处理1. 视频捕获基础2. 视频流属性设置与获取3. 视频写入 二、高级视频流操作1. 多摄像头处理2. 视频流帧处理3. 视频流分析与统计 三、其他视频处理库1. PyAV (FFmpeg 的 Python 绑定)2. imageio 四、视频流处理优化技巧1. 多线程视频处理2. 视…...

如何利用 QuickAPI 生成 PostgreSQL 样本测试数据:全面解析与实用指南
目录 一、什么是 QuickAPI? 二、为什么需要生成样本测试数据? 三、如何在 QuickAPI 中生成 PostgreSQL 样本测试数据? 1. 登录 QuickAPI 平台 2. 选择 PostgreSQL 数据库和目标表 3. 配置样本数据生成规则 4. 导出或直接插入数据 四、…...

DeepSeek API接口调用示例(开发语言C#,替换其中key值为自己的key值即可)
示例: DeepSeek官方接口说明文档:对话补全 | DeepSeek API Docs 官网暂未提供C#代码实现:(以下为根据CURL接口C#代码调用) using System; using System.Collections.Generic; using System.Linq; using System.Text; …...

远程调试---在电脑上devtools调试运行在手机上的应用
1、启动项目–以vite项目为例:先ipconfig查看ip地址 ,然后在vite中配置host为ip地址 2、手机上查看项目:保证手机和电脑在同一局域网, 在手机浏览器打开我们vite启动的项目地址, 3、使用chii进行远程调试 (1) 安装 npm install chii -g (2)启动 chii start -p 8080 (3)在…...

[git]如何关联本地分支和远程分支
主题 本文总结如何关联git本地分支和远程分支的相关知识点。 详情 查看本地分支 git branch 查看远程分支 git branch -r 查看所有分支(本地远程) git branch -a 查看本地分支及其关联的远程分支(如有) git branch -vv 关联本地分支到远程分支: git branch …...
集群/微服务/分布式
目录 介绍 集群 微服务 优点 缺点 如何管理和监控微服务架构中的多个微服务? 服务治理 配置管理 监控与告警 容器化与编排 安全管理 分布式 三者关系 分布式和集群的区别是什么? 概念 工作方式 节点角色 应用场景 故障处理 微服务 微…...
红黑树删除的实现与四种情况的证明
🧭 学习重点 删除节点的三种情况红黑树如何恢复性质四种修复情况完整可运行的 C 实现 一、红黑树删除的基础理解 红黑树删除比插入复杂得多,因为: 删除的是黑节点可能会破坏“从根到叶子黑节点数相等”的性质。删除红节点无需修复…...
【Spring AI 实战】基于 Docker Model Runner 构建本地化 AI 聊天服务:从配置到函数调用全解析
【Spring AI 实战】基于 Docker Model Runner 构建本地化 AI 聊天服务:从配置到函数调用全解析 前沿:本地化 AI 推理的新范式 随着大语言模型(LLM)应用的普及,本地化部署与灵活扩展成为企业级 AI 开发的核心需求。Do…...
前台--Android开发
在 Android 开发中,“前台(Foreground)” 是一个非常重要的概念,它用于描述当前用户正在与之交互的组件或应用状态。理解“前台”的含义有助于更好地管理资源、生命周期和用户体验。 ✅ 一、什么是前台? 简单定义&…...

跨境电商生死局:动态IP如何重塑数据生态与运营效率
凌晨三点的深圳跨境电商产业园,某品牌独立站运营总监李明(化名)正盯着突然中断的广告投放系统。后台日志显示,过去24小时内遭遇了17次IP封禁,直接导致黑五促销期间损失23%的预期流量。这并非个案——2023年跨境电商行业…...

springboot3+vue3融合项目实战-大事件文章管理系统-更新用户信息
在一下三个代码处进行修改 在UserController里面增加uadate方法 PutMapping ("/update")public Result update(RequestBody Validated User user){userService.update(user);return Result.success();}在userservice中增加update方法 void update(User user); 然…...

气象大模型光伏功率预测中的应用:从短期,超短期,中长期的实现与开源代码详解
1. 引言 光伏功率预测对于电力系统调度、能源管理和电网稳定性至关重要。随着深度学习技术的发展,大模型(如Transformer、LSTM等)在时间序列预测领域展现出强大能力。本文将详细介绍基于大模型的光伏功率预测方法,涵盖短期(1-6小时)、超短期(15分钟-1小时)和中长期(1天-1周…...

深度学习:智能车牌识别系统(python)
这是一个基于opencv的智能车牌识别系统,有GUI界面。程序能自动识别图片中的车牌号码,并支持中文和英文字符识别,支持选择本地图片文件,支持多种图片格式(jpg、jpeg、png、bmp、gif)。 下面,我将按模块功能对代码进行分段说明: 1. 导入模块部分 import tkinter as tk…...
[杂谈随感-13]: 人的睡眠,如何布置床的位置比较有安全?感?
睡眠环境中的床位布置直接影响心理安全感与睡眠质量,需从空间防御性、人体感知机制及环境心理学多维度综合设计。 以下基于科学原理与实践案例,系统解析床位布置的核心策略: 一、空间防御性布局:构建心理安全边界 背靠实体墙&a…...

DNS服务实验
该文章将介绍DNS服务的正向和反向解析实验、主从实验、转发服务器实验以及Web解析实验 正向解析实验:将域名解析为对应的IP地址 反向解析实验:将IP地址解析为对应的域名 主从实验:主服务器区域数据文件发送给从服务器,从服务器…...

visual studio 2015 安装闪退问题
参考链接: VS2012安装时启动界面一闪而过问题解决办法 visual studio 2015 安装闪退问题...

C语言复习--动态内存管理
下面我们来看C语言中的动态内存管理,在之后的数据结构中会运用到C语言中的指针,结构体和动态内存管理,所以这部分还是比较重要的.下面进入正题. 为什么要有动态内存分配 但是上面的两种方式开辟的内存的大小都是固定的.数组也是,在数组开辟之前一定要确定好数组大小,并且数组开…...