前端渲染pdf文件解决方案-pdf.js

news2025/4/25 3:20:06

目录

一、前言

二、简介

1、pdf.js介绍

2、插件版本参数

三、通过viewer.html实现预览(推荐)

1、介绍

2、部署

【1】下载插件包

【2】客户端方式

【3】服务端方式(待验证)

3、使用方法

【1】预览PDF文件

【2】外部搜索条件触发pdf.js的搜索逻辑

四、把pdf渲染为canvas实现预览

1、安装

2、功能实现

【1】实现pdf预览

【2】实现pdf内容文本可选进行复制

【3】实现搜索,匹配内容高亮,并且可以跳转至匹配内容的位置

【4】获取pdf文件中目录的数据结构


一、前言

        在前端开发中,渲染PDF文件一直是一项重要而挑战性的任务。而今,我们可以借助pdf.js库来轻松实现这一目标。pdf.js是一个开源的JavaScript库,它可以在浏览器中渲染PDF文件,实现了在网页上查看PDF文档的功能。它提供了丰富的API和功能,使得在前端页面展示PDF文件变得轻而易举。让我们一起探索pdf.js的奇妙之处,轻松实现前端PDF文件的渲染与展示吧!

二、简介

1、pdf.js介绍

        pdf.js是一款基于JavaScript的开源PDF阅读器组件,可以在网页中直接显示和操作PDF文件,目前已知的前端渲染pdf组件都是基于pdf.js进行封装。

git地址:https://github.com/mozilla/pdf.js

注:开源且免费

2、插件版本参数

插件版本
Nodev22.13.0

@types/react

^18.0.33

@types/react-dom

^18.0.11
pdfjs-2.5.207-es5-dist.zip (viewer.js使用方式)2.5.207
pdfjs-dist   (canvas渲染方式)

3.6.172

三、通过viewer.html实现预览(推荐)

1、介绍

        除了PDF预览,还待配套的工具栏,支持功搜索、缩放、目录、打印等功能~ 

Demo如图:

2、部署

【1】下载插件包

下载地址:https://github.com/mozilla/pdf.js/releases/tag/v2.5.207

【2】客户端方式

       把下载后的pdfjs-2.5.207-es5-dist.zip解压后,放在项目中的public文件夹下

【3】服务端方式(待验证)

   可将pdf.js包 放到服务器上 如:http://[ip]:[port]/static/pdfjs

3、使用方法

【1】预览PDF文件

        1)客户端方式(基于React框架为例)

const viewPDF: React.FC = () => {
    // pdf文件路径,放在项目的public目录下
    const pdfUrl = '/A.pdf'; 
    //pdf.js库的代码,放在项目的public目录下
    const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html'
    const url = `${pdfServerUrl}?file=${pdfUrl}`
  
    return <>
        <h1>pdf 搜索(基于pdf-dist,pdf_viewer.html)</h1>
        <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe>
      </>;
}

        2)服务端方式(待整理)

【2】外部搜索条件触发pdf.js的搜索逻辑

  • 跳转至第一个匹配的内容
  • 匹配内容高亮
const viewPDF: React.FC = () => {
    // pdf文件路径,放在项目的public目录下
    const pdfUrl = '/A.pdf'; 
    //pdf.js库的代码,放在项目的public目录下
    const pdfServerUrl = '/pdfjs-2.5.207-es5-dist/web/viewer.html'
    const url = `${pdfServerUrl}?file=${pdfUrl}`

    let pdfContentWindow: any = null; //缓存iframContent
    const getPdfContent = () => {
        const pdfFrame: any = document.getElementById('pdfIframe');
        if (!pdfFrame) {
          return;
        }
        pdfContentWindow = pdfFrame.contentWindow;
        //pdf组件部分信息,包括:当前页码、总共页码等
        console.log('page===>', pdfContentWindow.PDFViewerApplication); 
    }
    const onSearchForOut = (searchText: string) => {
        pdfContentWindow.postMessage(searchText, '*');
        pdfContentWindow.addEventListener('message', (e: any) => {
          
          // 高亮匹配结果
          pdfContentWindow.PDFViewerApplication.findBar.findField.value = e.data;
          pdfContentWindow.PDFViewerApplication.findBar.highlightAll.checked = true;                        pdfContentWindow.PDFViewerApplication.findBar.dispatchEvent('highlightallchange');
          //触发搜索项‘下一个’事件
          pdfContentWindow.PDFViewerApplication.findBar.dispatchEvent('again', false);

    }, false);
  }
  useEffect(() => {
    getPdfContent();
    setTimeout(() => {
      // 外部的搜索条件
      onSearchForOut('阳区CBD核心区')
    }, 3* 1000)
  }, []);  


    return <>
        <h1>pdf 搜索(基于pdf-dist,pdf_viewer.html)</h1>
        <iframe id='pdfIframe' src={url} width="100%" height="100%"></iframe>
      </>;
}

四、把pdf渲染为canvas实现预览

1、安装

npm install pdfjs-dist --save

2、功能实现

【1】实现pdf预览

import { Button } from 'antd';
import { useState, useEffect, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist'; // 引入pdfjs-dist
const pdfUrl = '/zyk.pdf'; // pdf 文件路径,pdf文件存放于public目录下
const workerUrl = `/pdf.worker.min.js`; //webworker存放于public目录下
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;

const viewPdf = (props: {height: string}) => {
  const {height} = props;
  const pdfContainerRef = useRef<any>(null);
  const [pagesList, setPagesList] = useState<any>([]);
  const scale = 2; // 缩放比例
  // 渲染单个页面
  const renderPage = async (page: any, pageNumber: number) => {
    const viewport = page.getViewport({ scale });
    const pageContentDom = document.createElement('div');
    pageContentDom.id = `pdfPage-content-${pageNumber}`;
    pageContentDom.style.width = `${viewport.width}px`;
    pageContentDom.style.height = `${viewport.height}px`;
    pageContentDom.style.position = 'relative';
    // 创建 Canvas 元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.id=`pdfPage-${pageNumber}`
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    canvas.style.border = '1px solid black';
    pageContentDom.appendChild(canvas);
    pdfContainerRef.current.appendChild(pageContentDom);
    // 渲染 PDF 页面到 Canvas
    await page.render({
      canvasContext: context,
      viewport,
    }).promise;
  };
  // 渲染 PDF 页面
  const renderPagesGroup = ( pages: any) => {
    pages.forEach(({page}:any, index: number) => {
      renderPage(page, index);
    });
  };
  // 加载 PDF 文件
  const loadPdf = async (url: any) => {
    const pdf = await pdfjsLib.getDocument(url).promise;
    const pages: any[] = [];
    for (let i = 1; i <= pdf.numPages; i++) {
      const page = await pdf.getPage(i);
      const textContent = await page.getTextContent();
      pages.push({
        page,
        textContent
      });
    }
    setPagesList(pages);
    renderPagesGroup(pages);
  };
  useEffect(() => {
    loadPdf(pdfUrl);
  }, []);
  return <>
      <div>
          <h1>PDF 搜索(基于@pdfjs-dist-自定义实现)</h1> 
      <div>
      <div style={{ height: height || '500px' }}>
        {/* PDF 容器 */}
        <div ref={pdfContainerRef} style={{ position: 'relative', height: '100%', overflowY: 'scroll' }} />
      </div>
      </div>
     </div>
    </>
};

export default viewPdf;

【2】实现pdf内容文本可选进行复制

...
//基于“【1】实现pdf预览”代码, 修改renderPage方法

// 渲染单个页面
 const renderPage = async (page: any, pageNumber: number) => {
    const viewport = page.getViewport({ scale });
    const pageContentDom = document.createElement('div');
    pageContentDom.id = `pdfPage-content-${pageNumber}`;
 //add-begin: 文本可选则  为了文本层和canvas层重叠,利用组件库的类名(类名不能修改)
 pageContentDom.className = 'pdfViewer';
 pageContentDom.style.setProperty('--scale-factor', scale as any);
 //add-end: 文本可选则
    pageContentDom.style.width = `${viewport.width}px`;
    pageContentDom.style.height = `${viewport.height}px`;
    pageContentDom.style.position = 'relative';
    // 创建 Canvas 元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.id=`pdfPage-${pageNumber}`
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    canvas.style.border = '1px solid black';
    pageContentDom.appendChild(canvas);
    createHeightLightCanvas(viewport, pageNumber, pageContentDom);
    pdfContainerRef.current.appendChild(pageContentDom);
    // 渲染 PDF 页面到 Canvas
    await page.render({
      canvasContext: context,
      viewport,
    }).promise;
 //add-begin: 文本可选则
    const textLayerDiv = document.createElement('div');
    textLayerDiv.style.width = viewport.width;
    textLayerDiv.style.height = viewport.height;
    //为了文本层和canvas层重叠,利用组件库的类名
    textLayerDiv.className = 'textLayer';
    const textContent = await page.getTextContent();
    pdfjsLib.renderTextLayer({
      textContentSource: textContent,
      container: textLayerDiv,
      viewport: viewport,
      textDivs: [],
    });
    pageContentDom.appendChild(textLayerDiv);
 //add-end: 文本可选则
  };

【3】实现搜索,匹配内容高亮,并且可以跳转至匹配内容的位置

import { Button } from 'antd';
import { useState, useEffect, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist'; // 引入pdfjs-dist
const pdfUrl = '/zyk.pdf'; // pdf 文件路径,pdf文件存放于public目录下
const workerUrl = `/pdf.worker.min.js`; //webworker存放于public目录下
pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl;

const viewPdf = (props: {height: string}) => {
   const {height} = props;
  const [searchText, setSearchText] = useState('');
  const pdfContainerRef = useRef<any>(null);
  const [pagesList, setPagesList] = useState<any>([]);
  const [matchList, setMatchList] = useState<any>([]);
  const scale = 2; // 缩放比例
  const createHeightLightCanvas = (viewport: any, pageNumber: number, parentDom: any) => {
    // 为每页创建一个高亮层canvas
    const highlightCanvas = document.createElement('canvas');
    highlightCanvas.id = `highlightCanvas-${pageNumber}`;
    highlightCanvas.className = 'highlightCanvas';
    highlightCanvas.width = viewport.width;
    highlightCanvas.height = viewport.height;
    highlightCanvas.style.position = 'absolute';
    highlightCanvas.style.top = '0';
    highlightCanvas.style.left = '0';
    highlightCanvas.style.zIndex = '1';
    parentDom.appendChild(highlightCanvas);
  }
  // pageNumber 页码(从0开始)
  const jumpToPage = (pageIndex: number) => {
    let beforeCanvasHeight = 0;
    for (let i = 0; i < pageIndex; i++) {
      const canvasParentDom = pdfContainerRef.current.querySelector(`#pdfPage-content-${i}`);
      let canvasParentHeight = canvasParentDom.style.height.replace('px', '');
      beforeCanvasHeight += Number(canvasParentHeight);
    }
    pdfContainerRef.current.scrollTo({
      top: beforeCanvasHeight, // 垂直滚动位置
      behavior: 'smooth'
    });
  }
  const getCurrentTextContentY = (canvas: any, match: any) => {
    // pdfjs 坐标系原点在左下角。transform[5]代表y轴的基线,所以需要减去高度
    const {textBlock} = match;
    const { transform, height } = textBlock;
    return canvas.height - (transform[5] + height -2) * scale;
  }
  // 滚动到指定的匹配项
  const scrollToMatch = (match: any) => {
    const { pageIndex, matchList } = match;
    const firstMatchContent = matchList[0];
    // 获取滚动区域的高度
    const scrollHeight = pdfContainerRef.current.scrollHeight;
    console.log('滚动区域的高度:', scrollHeight);
    // 获取当前页码之前dom的高度
    let beforePageHeight = 0;
    for (let i = 0; i < pageIndex; i++) {
      const canvasParentDom = pdfContainerRef.current.querySelector(`#pdfPage-content-${i}`);
      let canvasParentHeight = canvasParentDom.style.height.replace('px', '');
      beforePageHeight += Number(canvasParentHeight);
    }
    // todo 继续计算 匹配项目的高度
    const currentPageCanvas = pdfContainerRef.current.querySelector(`#pdfPage-${pageIndex}`);
    const textContentY = getCurrentTextContentY(currentPageCanvas, firstMatchContent);
    const offsetTop = 50; //为了滚动目标文字不顶格
    const targetScrollTop = beforePageHeight + textContentY -offsetTop;
    pdfContainerRef.current.scrollTo({
      top: targetScrollTop, // 垂直滚动位置
      behavior: 'smooth'
    });
  };
  // 绘制高亮区域
  const drawHighlights = async (canvas: any, matchesList: MatchBlockItem[]) => {
    if (matchesList.length === 0) {
      return;
    }
    const context = canvas.getContext('2d');
    context.fillStyle = 'rgba(255, 255, 0, 0.5)'; // 黄色半透明填充
    matchesList.forEach((match: any) => {
      const {textBlock} = match;
      const { transform, width, height, str } = textBlock;
      // 获取每一个字符的宽度
      const charWidth = width / str.length;
      const lightWidth = (match.textEndIndex - match.textStartIndex) * charWidth;
      const lightHeight = height;
      const x = transform[4] + match.textStartIndex * charWidth;
      const y = getCurrentTextContentY(canvas, match);
      context.fillRect(
        Math.floor(x * scale), 
        Math.floor(y), 
        Math.ceil(lightWidth * scale), 
        Math.ceil(lightHeight * scale)
      );
    });
  };
  // 渲染单个页面
  const renderPage = async (page: any, pageNumber: number) => {
    const viewport = page.getViewport({ scale });
    const pageContentDom = document.createElement('div');
    pageContentDom.id = `pdfPage-content-${pageNumber}`;
    //为了文本层和canvas层重叠,利用组件库的类名
    pageContentDom.className = 'pdfViewer';
    pageContentDom.style.setProperty('--scale-factor', scale as any);
    pageContentDom.style.width = `${viewport.width}px`;
    pageContentDom.style.height = `${viewport.height}px`;
    pageContentDom.style.position = 'relative';
    // 创建 Canvas 元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.id=`pdfPage-${pageNumber}`
    canvas.width = viewport.width;
    canvas.height = viewport.height;
    canvas.style.border = '1px solid black';
    pageContentDom.appendChild(canvas);
    createHeightLightCanvas(viewport, pageNumber, pageContentDom);
    pdfContainerRef.current.appendChild(pageContentDom);
    // 渲染 PDF 页面到 Canvas
    await page.render({
      canvasContext: context,
      viewport,
    }).promise;
    // 渲染文本框
    const textLayerDiv = document.createElement('div');
    textLayerDiv.style.width = viewport.width;
    textLayerDiv.style.height = viewport.height;
    //为了文本层和canvas层重叠,利用组件库的类名
    textLayerDiv.className = 'textLayer';
    const textContent = await page.getTextContent();
    pdfjsLib.renderTextLayer({
      textContentSource: textContent,
      container: textLayerDiv,
      viewport: viewport,
      textDivs: [],
    });
    pageContentDom.appendChild(textLayerDiv)
  };
  // 渲染 PDF 页面
  const renderPagesGroup = ( pages: any) => {
    pages.forEach(({page}:any, index: number) => {
      renderPage(page, index);
    });
  };
  // 加载 PDF 文件
  const loadPdf = async (url: any) => {
    const pdf = await pdfjsLib.getDocument(url).promise;
    const pages: any[] = [];
    for (let i = 1; i <= pdf.numPages; i++) {
      const page = await pdf.getPage(i);
      const textContent = await page.getTextContent();
      pages.push({
        page,
        textContent
      });
    }
    setPagesList(pages);
    renderPagesGroup(pages);
  };
  const findAllMatches = (text: string, pattern: string) => {
    // 创建正则表达式对象
    const regex = new RegExp(pattern, 'g');
    // 使用match方法找到所有匹配项
    const matches = text.match(regex);
    // 如果没有匹配项,返回空数组
    if (!matches) {
        return [];
    }
    // 创建一个数组来存储所有匹配的位置
    const positions = [];
    // 遍历所有匹配项,找到它们在字符串中的位置
    let match;
    while ((match = regex.exec(text)) !== null) {
      positions.push(match.index);
    }
    return positions;
  }

  // todo 优化参数个数,
  const getMatchesList = (
    items: any,
    currentItem: any, 
    currentItemIndex: number,
    currentTextIndex: number, 
    searchStr: string): MatchBlockItem[] => {
    let matchSearchList: MatchBlockItem[] = [];
    if(currentItem.str.length - (currentTextIndex + 1) < searchStr.length -1 ) {
      // 获取当前文本块中剩余字符,如果小于搜索字符长度,则继续查找下一个文本块
      let itemText = currentItem.str.slice(currentTextIndex); // 获取当前文本块中剩余字符
      let tempMatchSearchList = [{
        blockIndex: currentItemIndex,
        textStartIndex: currentTextIndex,
        textEndIndex: currentItem.str.length,// 由于统一使用slice截取,所以不包括最后一位
        textBlock: currentItem
      }]; // 存储后续文本块
      let index = currentItemIndex;
      const otherSearchLength = searchStr.length -1;
      while (itemText.length <= otherSearchLength) {
        index = index + 1;
        const currentOtherSearchLength = otherSearchLength - itemText.length; // 当前剩余搜索字符长度
        if (items[index].str.length > currentOtherSearchLength) {
          // 文本块的长度大于剩余搜索字符长度,则截取剩余搜索字符长度的字符
          itemText = `${itemText}${items[index].str.slice(0, currentOtherSearchLength+1)}`;
          tempMatchSearchList.push({
            blockIndex: index,
            textStartIndex: 0,
            textEndIndex: currentOtherSearchLength + 1,
            textBlock: items[index]
          })
        } else {
          // 文本块的长度小于剩余搜索字符长度,则截取全部字符, 继续
          itemText = `${itemText}${items[index].str}`;
          tempMatchSearchList.push({
            blockIndex: index,
            textStartIndex: 0,
            textEndIndex: items[index].str.length,
            textBlock: items[index]
          })
        }
      }
      if (itemText === searchStr) {
        matchSearchList = matchSearchList.concat(tempMatchSearchList);
      }
    }
    else {
      // 获取当前文本块中剩余字符,如果大于等于搜索字符长度,则截取当前文本块中搜索文本长度的字符
      const textEndIndex = currentTextIndex + searchStr.length;
      const text = currentItem.str.slice(currentTextIndex, textEndIndex); // 取出匹配字符所在文本块及后续文本块
      if (text === searchStr) {
        console.log('匹配到了:', currentItem, currentItemIndex)
        matchSearchList.push({
          blockIndex: currentItemIndex,
          textStartIndex: currentTextIndex,
          textEndIndex: textEndIndex,
          textBlock: currentItem
        })
      }
    }
    return matchSearchList;
  }

  // 查找文本的所有出现位置
  const findAllOccurrences = (items: any, searchStr: string): MatchBlockItem[] => {
    const firstSearchStr = searchStr[0];
    let matchSearchList: MatchBlockItem[] = [];
    for(let i=0; i<items.length; i++) {
      const currentItem = items[i];
      const currentMatchIndexList = findAllMatches(currentItem.str, firstSearchStr); // 获取当前文本块中第一个匹配字符的索引列表
      if (currentMatchIndexList.length > 0) {
        for(let j=0; j<currentMatchIndexList.length; j++){
          matchSearchList = [...matchSearchList, ...getMatchesList(items, currentItem, i, currentMatchIndexList[j], searchStr)];
        }
      }
    }
    return matchSearchList;
  };
  const clearHeightLightsCanvas = () => {
    const highlightCanvases = Array.from(pdfContainerRef.current.querySelectorAll('.highlightCanvas'));
    highlightCanvases.forEach((canvas: any) => {
      const context = canvas.getContext('2d');
      context.clearRect(0, 0, canvas.width, canvas.height);
    });
  }
  const handleSearch = async () => {
    clearHeightLightsCanvas()
    if (!searchText) {
      jumpToPage(0);
      return;
    }
    const newMatches: any = [];
    console.log('pagesList', pagesList)
    // todo  目前是按照每页来匹配,可能会匹配不到跨页的内容
    pagesList.forEach(async ({textContent}: any, pageIndex: number) => {
      const pageMatches = findAllOccurrences(textContent.items, searchText);
      newMatches.push({
        pageIndex, // 页面索引
        matchList: pageMatches, // 匹配项列表
      });
    })
    console.log('newMatches', newMatches);
    const isNotMatch = newMatches.every((match: any) => match.matchList.length === 0);
    if (isNotMatch) {
      alert('未找到匹配项');
      return;
    }
    /// 重新绘制高亮区域
    pagesList.forEach((_: any, pageIndex: number) => {
      const highlightCanvas = pdfContainerRef.current.querySelectorAll('.highlightCanvas')[pageIndex]; // 获取高亮层 Canvas
      const currentMatches = newMatches.find((match: any) => match.pageIndex === pageIndex);
      drawHighlights(
        highlightCanvas,
        currentMatches?.matchList || []
      );
    });
    // 跳转
    const isExistItem = newMatches.find((match: any) => match.matchList.length > 0);
    if (isExistItem) {
      scrollToMatch(isExistItem);
    }
  };
   // 初始化 PDF.js
   useEffect(() => {
    loadPdf(pdfUrl);
  }, []);
  return <>
      <div>
      <h1>PDF 搜索(基于@pdfjs-dist-自定义实现)</h1>
      <input
        type="text"
        value={searchText}
        onChange={(e) => setSearchText(e.target.value)}
        placeholder="输入要搜索的内容"
      />
        <Button onClick={handleSearch}>搜索</Button>
      <div>
      <div style={{ height: height || '500px' }}>
        {/* PDF 容器 */}
        <div ref={pdfContainerRef} style={{ position: 'relative', height: '100%', overflowY: 'scroll' }} />
      </div>
      </div>
     </div>
    </>
};

export default viewPdf;

【4】获取pdf文件中目录的数据结构

....
//基于‘【1】实现pdf预览’的代码
  const get= async (url: any) => {
    const pdf = await pdfjsLib.getDocument(url).promise;
    // 获取目录数据
    const pdfCatalogue= await pdf.getOutline();
    console.log('目录数据:', pdfCatalogue); 
  };
...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2342104.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从边缘到云端,如何通过时序数据库 TDengine 实现数据的全局洞

在当今数字化转型加速的背景下&#xff0c;海量的数据生成和实时处理需求已成为企业面临的关键挑战。无论是物联网设备、工业自动化系统&#xff0c;还是智能城市的各类传感器&#xff0c;数据的采集、传输与分析效率&#xff0c;直接影响企业的决策与运营。为此&#xff0c;TD…

2025.04.23【Treemap】树状图数据可视化指南

Multi-level treemap How to build a treemap with group and subgroups. Customization Customize treemap labels, borders, color palette and more 文章目录 Multi-level treemapCustomization Treemap 数据可视化指南Treemap 的基本概念为什么使用 TreemapTreemap 的应用…

蓝桥杯 15.小数第n位

小数第n位 原题目链接 题目描述 我们知道&#xff0c;整数做除法时&#xff0c;有时会得到有限小数&#xff0c;有时会得到无限循环小数。 如果我们把有限小数的末尾加上无限多个 0&#xff0c;它们就具有了统一的形式。 本题的任务是&#xff1a;在上述约定下&#xff0c…

用高斯溅射技术跨越机器人模拟与现实的鸿沟:SplatSim 框架解析

在机器人领域&#xff0c;让机器人在现实世界中精准执行任务是大家一直追求的目标。可模拟环境和现实世界之间存在着不小的差距&#xff0c;特别是基于 RGB 图像的操作策略&#xff0c;从模拟转移到现实时总是状况百出。 今天咱们就来聊聊 SplatSim 框架&#xff0c;看看它是怎…

AI大模型学习十一:‌尝鲜ubuntu 25.04 桌面版私有化sealos cloud + devbox+minio,实战运行成功

一、说明 用了ubuntu 25.04&#xff0c;内核为GNU/Linux 6.14.0-15-generic x86_64&#xff0c;升级了部分image&#xff0c;过程曲折啊 sealos 能干啥 对集群生命周期进行管理&#xff0c;一键安装高可用 Kubernetes 集群&#xff0c;增删节点清理集群自恢复等 通过 sealos…

如何在 Python 项目中引入 Rust 函数

目录 1. 初始化 Python 项目2. 添加 Rust 开发工具3. 初始化 Rust 项目4. 开发模式构建5. 验证模块是否成功安装6. 测试 Rust 函数总结 (封面pid: 129416070) Python 是一门非常流行的编程语言&#xff0c;具有易于使用和开发的特点。然而&#xff0c;随着项目需求的增长和性能…

聊聊SpringAI流式输出的底层实现?

在 Spring AI 中&#xff0c;流式输出&#xff08;Streaming Output&#xff09;是一种逐步返回 AI 模型生成结果的技术&#xff0c;允许服务器将响应内容分批次实时传输给客户端&#xff0c;而不是等待全部内容生成完毕后再一次性返回。 这种机制能显著提升用户体验&#xff…

MySQL 8 自动安装脚本(CentOS-7 系统)

文章目录 一、MySQL 8 自动安装脚本脚本说明&#x1f4cc; 使用脚本前提条件1. 操作系统2. 用户权限3. 网络要求 &#x1f4cc; 脚本的主要功能1. 环境检查2. MySQL 自动安装3. 自动配置 MySQL4. 防火墙配置5. 验证与输出 &#x1f4cc; 适用场景 二、执行sh脚本1. 给予脚本执行…

在Notepad++中使用NppAtyle插件格式化代码

参考链接&#xff1a;Artistic Style 使用教程&#xff08;中文版&#xff09; 1.下载NppAStyle插件&#xff08;根据版本&#xff0c;选择32位或者64位&#xff09; https://github.com/ywx/NppAStyle/releases 2.菜单栏中选择&#xff1a;插件->打开插件文件夹 创建文件夹…

拼多多面经,暑期实习Java一面

项目中的设计模式 mysql连接过程&#xff0c;索引&#xff0c;分库分表场景&#xff0c;路由策略 redis使用场景&#xff0c;分片集群怎么搭建与路由&#xff0c;数据一致性 分布式锁怎么用的&#xff0c;具体使用参数 线程池怎么用的&#xff0c;过程 sql having 分布式事务 如…

FramePack:让视频生成更高效、更实用

想要掌握如何将大模型的力量发挥到极致吗&#xff1f;叶梓老师带您深入了解 Llama Factory —— 一款革命性的大模型微调工具&#xff08;限时免费&#xff09;。 1小时实战课程&#xff0c;您将学习到如何轻松上手并有效利用 Llama Factory 来微调您的模型&#xff0c;以发挥其…

ctfshow web8

前言 学习内容&#xff1a;简单的盲注脚本的书写 web8 这个题目题目手动注入很麻烦 主要是他过滤了 union 空格和 过滤了union的解决方法 1、使用盲注(报错注入和盲注) 2、使用时间盲注 3、堆叠注入 盲注脚本的书写 首先他是有注入点的 然后熟悉requests包的使用 …

gem5-gpu教程03 当前的gem5-gpu软件架构(因为涉及太多专业名词所以用英语表达)

Current gem5-gpu Software Architecture 这是当前gem5-gpu软件架构的示意图。 Ruby是在gem5-gpu上下文中用于处理CPU和GPU之间内存访问的高度可配置的内存系统 CudaCore (src/gpu/gpgpu-sim/cuda_core.*, src/gpu/gpgpu-sim/CudaCore.py) Wrapper for GPGPU-Sim shader_cor…

TDengine 查询引擎设计

简介 TDengine 作为一个高性能的时序大数据平台&#xff0c;其查询与计算功能是核心组件之一。该平台提供了丰富的查询处理功能&#xff0c;不仅包括常规的聚合查询&#xff0c;还涵盖了时序数据的窗口查询、统计聚合等高级功能。这些查询计算任务需要 taosc、vnode、qnode 和…

【官方正版,永久免费】Adobe Camera Raw 17.2 win/Mac版本 配合Adobe22-25系列软

Adobe Camera Raw 2025 年 2 月版&#xff08;版本 17.2&#xff09;。目前为止最新版新版已经更新2个月了&#xff0c;我看论坛之前分享的还是2024版&#xff0c;遂将新版分享给各位。 Adobe Camera Raw&#xff0c;支持Photoshop&#xff0c;lightroom等Adobe系列软件&#…

【论文精读】Reformer:高效Transformer如何突破长序列处理瓶颈?

目录 一、引言&#xff1a;当Transformer遇到长序列瓶颈二、核心技术解析&#xff1a;从暴力计算到智能优化1. 局部敏感哈希注意力&#xff08;LSH Attention&#xff09;&#xff1a;用“聚类筛选”替代“全量计算”关键步骤&#xff1a;数学优化&#xff1a; 2. 可逆残差网络…

jmeter中监控服务器ServerAgent

插件下载&#xff1a; 将ServerAgent上传至需要监控的服务器&#xff0c;mac/liunx启动startAgent.sh&#xff08;启动命令&#xff1a;./startAgent.sh&#xff09; 在jmeter中添加permon监控组件 配置需要监控的服务器IP地址&#xff0c;添加需要监控的资源 注意&#xf…

网络结构及安全科普

文章目录 终端联网网络硬件基础网络协议示例&#xff1a;用户访问网页 OSI七层模型网络攻击&#xff08;Hack&#xff09;网络攻击的主要类别&#xff08;一&#xff09;按攻击目标分类&#xff08;二&#xff09;按攻击技术分类 网络安全防御 典型攻击案例相关名词介绍网络连接…

JVM虚拟机-JVM调优、内存泄漏排查、CPU飙高排查

一、JVM调优的参数在哪里设置 项目开发过程中有以下两种部署项目的方式&#xff1a; 项目部署在tomcat中&#xff0c;是一个war包&#xff1b;项目部署在SpringBoot中&#xff0c;是一个jar包。 (1)war包 catalina文件在Linux系统下的tomcat是以sh结尾&#xff0c;在windows系…

安全复健|windows常见取证工具

写在前面&#xff1a; 此博客仅用于记录个人学习内容&#xff0c;学识浅薄&#xff0c;若有错误观点欢迎评论区指出。欢迎各位前来交流。&#xff08;部分材料来源网络&#xff0c;若有侵权&#xff0c;立即删除&#xff09; 取证 01系统运行数据 使用工具&#xff1a;Live-F…