当前位置: 首页 > news >正文

鸿蒙ArkTs如何实现pdf预览功能?

鸿蒙ArkTs如何实现pdf预览功能?

    • 前言
    • PDFKit运行示例代码报错
    • 真机运行
    • 先看效果
    • 一、预览本地pdf文件
    • 二、预览线上的pdf文件
    • 三、预览沙箱目录中pdf的文件(重点)
    • 效果中的整体代码
    • 总结
  • Harmony OS NEXT版本(接口及解决方案兼容API12版本或以上版本)

前言

在开发鸿蒙App时,你是否做过pdf预览功能。是否也和我一样碰壁了,来看看我遇到的问题,以及我是如何实现的吧。

PDFKit运行示例代码报错

the requested module '@hms:officeservice.PdfView' does not provide an export name 'pdfViewManager' which imported by 'xxxx'

真机运行

本来以为用真机就能运行了,没想到还是报错
在这里插入图片描述
那么下面来看看我是如何实现的吧

先看效果

在这里插入图片描述
视频转完gif,视觉上看起来有点卡,实际运行不卡。

一、预览本地pdf文件

预览本地的pdf文件很简单,使用Web组件加载即可。
pdf文件目录:harmonyApp\entry\src\main\resources\rawfile\test.pdf
具体代码如下:

import web_webview from '@ohos.web.webview';@Entry
@Component
struct Index {webviewController: web_webview.WebviewController = new web_webview.WebviewController();build() {Column() {// src-本地pdf文件Web({ src: $rawfile('test.pdf'), controller: this.webviewController }).layoutWeight(1).domStorageAccess(true)}.height('100%')}
}

二、预览线上的pdf文件

这里的线上的pdf文件是指可以在浏览器直接打开预览的pdf文件,还有一种是在浏览器打开是直接进入下载的,那么就需要我们进一步处理了,第三点有详解。
这样的文件预览也很简单,使用Web组件加载即可。
具体代码如下:

import web_webview from '@ohos.web.webview';@Entry
@Component
struct Index {webviewController: web_webview.WebviewController = new web_webview.WebviewController();build() {Column() {// 线上pdf链接Web({ src: 'http://www.cztouch.com/upfiles/soft/testpdf.pdf', controller: this.webviewController }).layoutWeight(1).domStorageAccess(true)}.height('100%')}
}

三、预览沙箱目录中pdf的文件(重点)

这种就比较麻烦了,有的pdf链接在浏览器打开直接跳转下载不会预览,那么就需要我们下载到沙箱目录中,再预览沙箱目录中的pdf文件。
我这里用到了一个pdfviewer工具,可从我的百度网盘免费获取
拿到文件夹后,放在以下目录:
项目目录:harmonyApp\entry\src\main\resources\rawfile
具体实现代码如下:

import router from '@ohos.router';
import web_webview from '@ohos.web.webview';
import { BusinessError, request } from '@kit.BasicServicesKit';
import showToast from '../../common/utils/ToastUtils';
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';interface IBase64 {base64: string;fileName: string;
}@Entry
@Component
struct Index2 {controller: web_webview.WebviewController = new web_webview.WebviewController()// pdf文件路径@State fileUrl: string = ''// 本地沙箱文件地址@State tempFilePath: string = ''// 是否显示按钮@State isShowBtn: boolean = true;build() {Stack() {Column() {// 页面内容Scroll(){Column(){if(this.tempFilePath){if(this.isShowBtn){Button('打开文件').onClick(()=>{this.isShowBtn = false;})}else{Web({ src: $rawfile('pdfviewer/viewer.html'), controller: this.controller }).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)}).domStorageAccess(true) // 设置是否开启文档对象模型存储接口(DOM Storage API)权限,默认未开启。.onPageEnd(()=>{let file = this.sandBoxPdfToBase64(this.tempFilePath);this.controller.runJavaScript(`openFile("${file.base64}", "${file.fileName}")`);})}}}.width('100%').height('100%')}.edgeEffect(EdgeEffect.Fade).width('100%').layoutWeight(1).align(Alignment.TopStart)}.height('100%').backgroundColor(Color.White)}}// 沙箱pdf文件转base64方法sandBoxPdfToBase64(url: string) {let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件let stat = fs.statSync(url); // 获取文件状态let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象let base64 = new util.Base64Helper(); // 实例化Base64Helperlet num = fs.readSync(file.fd, buf); // 读取文件let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) //  转换成Uint8Arraylet textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64let fileName = file.namefs.closeSync(file);return { base64: retStr, fileName: fileName } as IBase64;}// 下载pdf文件,获取沙箱文件目录getTempFile(fileUrl:string){let context = getContext(this) as common.UIAbilityContext;const fileFullName = fileUrl.split('/')[fileUrl.split('/').length - 1]let tempFilePath = `${context.filesDir}/${fileFullName}`;//文件如果已经存在,就删除if (fs.accessSync(tempFilePath)) {fs.unlink(tempFilePath)}request.downloadFile(getContext(), { url: fileUrl,filePath: tempFilePath }).then((data: request.DownloadTask) => {let downloadTask: request.DownloadTask = data;let progressCallback = (receivedSize: number, totalSize: number) => {// 这里可以自行编写下载进度条showToast(`下载大小${receivedSize},总大小${totalSize}`);};let completeCallback = ()=>{showToast("下载完毕");this.tempFilePath = tempFilePath;}downloadTask.on('progress', progressCallback);downloadTask.on('complete', completeCallback)}).catch((err: BusinessError) => {console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);})}// 组件生命周期:组件即将出现时回调该接口aboutToAppear() {console.log('进入页面')// 你的pdf链接this.fileUrl = (router.getParams() as Record<string, string>).url || '';this.getTempFile((router.getParams() as Record<string, string>).url as string);}
}

这里附有将pdf文件下载到沙箱目录代码,可选择使用(不必须)。

效果中的整体代码

import web_webview from '@ohos.web.webview';
import promptAction from '@ohos.promptAction'
import { BusinessError, request } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';// pdf页面tab接口
interface pageTab {name:string;
}interface IBase64 {base64: string;fileName: string;
}/*** pdfPage的ViewModel*/
class PdfPageModel {// 当前索引curTabIndex:number = 0;// pdf页面tabtabList:pageTab[] = [{ name:'预览本地PDF文件' },{ name:'预览网络PDF文件' },{ name:'预览沙箱PDF文件' },];// 网络文件fileUrl: string = 'http://www.cztouch.com/upfiles/soft/testpdf.pdf'// 本地沙箱文件地址tempFilePath: string = ''constructor() {}// 沙箱pdf文件转base64方法sandBoxPdfToBase64(url: string) {let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件let stat = fs.statSync(url); // 获取文件状态let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象let base64 = new util.Base64Helper(); // 实例化Base64Helperlet num = fs.readSync(file.fd, buf); // 读取文件let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) //  转换成Uint8Arraylet textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64let fileName = file.namefs.closeSync(file);return { base64: retStr, fileName: fileName } as IBase64;}// 下载pdf文件,获取沙箱文件目录getTempFile(fileUrl:string){let context = getContext(this) as common.UIAbilityContext;const fileFullName = fileUrl.split('/')[fileUrl.split('/').length - 1]let tempFilePath = `${context.filesDir}/${fileFullName}`;//文件如果已经存在,就删除if (fs.accessSync(tempFilePath)) {fs.unlink(tempFilePath)}request.downloadFile(getContext(), { url: fileUrl,filePath: tempFilePath }).then((data: request.DownloadTask) => {let downloadTask: request.DownloadTask = data;let progressCallback = (receivedSize: number, totalSize: number) => {// 这里可以自行编写下载进度条// showToast(`下载大小${receivedSize},总大小${totalSize}`);};let completeCallback = ()=>{// showToast("下载完毕");this.tempFilePath = tempFilePath;}downloadTask.on('progress', progressCallback);downloadTask.on('complete', completeCallback)}).catch((err: BusinessError) => {console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);})}// tab切换switchTab(index:number){this.curTabIndex = index;if(index === 2 && !this.tempFilePath){try {promptAction.showDialog({title: '温馨提示',message: '有些pdf线上链接是经过第三方加密过的,在浏览器访问时不能直接预览,直接走的是下载的pdf文件链接,可以采用这种方式,先下载在沙箱目录中,然后再预览沙箱中的pdf文件',buttons: [{text: '知道了',color: '#000000'}]}, (err, data) => {if (err) {console.error('showDialog err: ' + err);return;}console.info('showDialog success callback, click button: ' + data.index);});} catch (error) {console.error(`Failed to show dialog. Code: ${error.code}, message: ${error.message}`);}this.getTempFile(this.fileUrl);}}
}@Entry
@Component
struct PdfPage {webviewController: web_webview.WebviewController = new web_webview.WebviewController();@State vm: PdfPageModel = new PdfPageModel();// 验证是否选中VerifySelectedFun( curIndex:number , itemIndex:number ):boolean{return curIndex == itemIndex}aboutToAppear(): void {try {promptAction.showDialog({title: '温馨提示',message: '在模拟器中运行,首次加载会出现黑屏,但来回切换几次tab标签就好了,有条件的建议使用真机运行,不会有这样的问题',buttons: [{text: '知道了',color: '#000000'}]}, (err, data) => {if (err) {console.error('showDialog err: ' + err);return;}console.info('showDialog success callback, click button: ' + data.index);});} catch (error) {console.error(`Failed to show dialog. Code: ${error.code}, message: ${error.message}`);}}build() {Stack() {Column() {// tab标签条Row(){Scroll(){Row(){ForEach(this.vm.tabList,(item:pageTab,index)=>{Row(){if(this.VerifySelectedFun(this.vm.curTabIndex,index)){Stack(){Row(){}.width(40).height(10).borderRadius(20).offset({y:7}).linearGradient({angle:89.11,colors:[['rgba(255, 255, 255, 0.55)',0.0682],['rgba(217, 217, 217, 0)',1]]})Text(item.name).fontSize(18).fontColor($r('app.color.primary_theme_color')).fontWeight(600).height('100%')}}else{Text(item.name).fontSize(16)// .fontColor($r('app.color.font_color_default')).fontWeight(400)}}.height('100%').justifyContent(FlexAlign.Start).padding({ left: index == 0 ? 0 : 20 }).onClick(()=>{this.vm.switchTab(index)})})}}.edgeEffect(EdgeEffect.Fade).layoutWeight(1).align(Alignment.Center).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)}.width('100%').height(50).justifyContent(FlexAlign.Start).padding({left:16,right:16}).backgroundColor(Color.White)// 页面内容Scroll(){Column(){if(this.vm.curTabIndex === 0 ){// web组件加载本地pdf文件Web({ src: $rawfile('Git.pdf'), controller: this.webviewController}).domStorageAccess(true).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)})}else if(this.vm.curTabIndex === 1){// web组件加载网络pdf文件Web({ src: 'http://www.cztouch.com/upfiles/soft/testpdf.pdf', controller: this.webviewController }).layoutWeight(1).domStorageAccess(true).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)})}else if(this.vm.curTabIndex === 2){if(this.vm.tempFilePath){Web({ src: $rawfile('pdfviewer/viewer.html'), controller: this.webviewController }).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)}).domStorageAccess(true) // 设置是否开启文档对象模型存储接口(DOM Storage API)权限,默认未开启。.onPageEnd(()=>{let file = this.vm.sandBoxPdfToBase64(this.vm.tempFilePath);this.webviewController.runJavaScript(`openFile("${file.base64}", "${file.fileName}")`);})}}}.padding({ left: 16, right: 16, bottom: 16 })}.edgeEffect(EdgeEffect.Fade).width('100%').layoutWeight(1).align(Alignment.TopStart)}.height('100%').backgroundColor('#F5F5F5').padding({ bottom: 16 })}}
}

总结

总体来说就是使用Web组件加载pdf文件,在模拟器中运行,首次运行会黑屏,不过来回切换一下tab页就好了,真机运行没有问题。
为啥要存到沙箱中再预览,岂不是多此一举?
当然不是,因为有的pdf文件是通过第三方加密过的,在浏览器打开链接时,是不能直接预览的,而是直接走下载了。这时,就需要先存到沙箱目录中再预览。

有需要的朋友,拿走不谢,求赞,求赞,求赞~
关注我不迷路,不定时分享鸿蒙难点亮点

Harmony OS NEXT版本(接口及解决方案兼容API12版本或以上版本)

相关文章:

鸿蒙ArkTs如何实现pdf预览功能?

鸿蒙ArkTs如何实现pdf预览功能&#xff1f; 前言PDFKit运行示例代码报错真机运行先看效果一、预览本地pdf文件二、预览线上的pdf文件三、预览沙箱目录中pdf的文件(重点)效果中的整体代码总结 Harmony OS NEXT版本&#xff08;接口及解决方案兼容API12版本或以上版本) 前言 在开…...

KylinSP3 | 防火墙和麒麟安全增强设置KySec

一、系统防火墙原理 麒麟操作系统从V10版本开始&#xff0c;默认使用了Firewalld防火墙&#xff0c;Firewalld是能提供动态管理的防火墙&#xff0c;支持网络/防火墙区域&#xff0c;用于定义网络连接或接口的信任级别。支持IPv4和IPv6防火墙设置、以太网桥接和IP集。将运行时…...

【C++】面试常问八股

5、内存管理 野指针 野指针指的是未进行初始化或未清零的指针&#xff0c;不是NULL指针野指针产生原因及解决方案&#xff1a; 指针变量未初始化&#xff1a;指针变量定义时若未初始化&#xff0c;则其指向的地址是随机的&#xff0c;不为NULL&#xff1b;定义时初始化为NULL…...

vscode多文件编译构建(CMake)和调试C++

目录 1. CMake 基础构建工具及作用相关配置文件 2. 配置 tasks.json关键字段详细解释 3. 配置 launch.json关键字段详细解释 4. 配置 CMakeLists.txt关键部分详细解释 5. 构建和调试项目1. 仅构建项目1.1 任务执行顺序1.2 cmake 任务执行详情1.3 build 任务执行详情1.4 构建后的…...

使用Docker 部署 LNMP+Redis 环境

使用Docker 部署 LNMPRedis 环境 Docker 简介 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互…...

文件上传漏洞学习笔记

一、漏洞概述 定义 文件上传漏洞指未对用户上传的文件进行充分安全校验&#xff0c;导致攻击者可上传恶意文件&#xff08;如Webshell、木马&#xff09;&#xff0c;进而控制服务器或执行任意代码。 危害等级 ⚠️ 高危漏洞&#xff08;通常CVSS评分7.0&#xff09;&#xff…...

375_C++_cloud手机推送,添加人脸告警信息到任务队列中,UploadAlarmPush是典型的工厂模式应用,为什么使用工厂模式完成这部分代码

一:AlarmFaceInfo的应用 让我帮你解析这个lambda表达式的实现: // ...................... .h ...........................// struct RsMsgPushTask_S : public Task{AlarmType_E mainAlarmType;unsigned int subAlarmType;DateTime alarmTime...

Spring Boot 中的日志管理

一、日志框架选择 1. 主流框架对比 框架特点Spring Boot 默认支持Logback- 性能优异&#xff0c;Spring Boot 默认集成- 支持自动热更新配置文件✅ (默认)Log4j2- 异步日志性能更强- 支持插件扩展- 防范漏洞能力更好❌ (需手动配置)JUL (JDK自带)- 无需额外依赖- 功能简单&am…...

火绒终端安全管理系统V2.0网络防御功能介绍

火绒终端安全管理系统V2.0 【火绒企业版V2.0】网络防御功能包含网络入侵拦截、横向渗透防护、对外攻击检测、僵尸网络防护、Web服务保护、暴破攻击防护、远程登录防护、恶意网址拦截。火绒企业版V2.0的网络防御功能&#xff0c;多层次、多方位&#xff0c;守护用户终端安全。 …...

海康摄像头 + M7s(Monibuca) + FFmpeg + Python实现多个网络摄像头视频流推流

最近在研究流媒体服务器时&#xff0c;我注意到了一款开源软件——M7s。按照官网的指南部署完成后&#xff0c;我开始进行测试&#xff0c;发现单视频流推送非常顺利&#xff0c;没有任何问题。然而&#xff0c;当我尝试进行多视频流推送时&#xff0c;却发现网上的相关教程寥寥…...

抖音视频如何下载保存去水印

随着短视频平台的兴起&#xff0c;抖音作为国内最受欢迎的短视频平台之一&#xff0c;吸引了大量用户上传和观看各种创意视频。许多用户在浏览抖音视频时&#xff0c;往往会想要保存一些有趣或精彩的视频片段&#xff0c;但抖音视频通常会有水印&#xff0c;影响观看体验。为了…...

【鸿蒙开发】第三十九章 LazyForEach:数据懒加载

目录 1 背景 2 使用限制 键值生成规则 组件创建规则 首次渲染 非首次渲染 改变数据子属性 使用状态管理V2 拖拽排序 1 背景 LazyForEach从提供的数据源中按需迭代数据&#xff0c;并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach&#xff0c;框架…...

HTTP-

一.HTTP 1.什么是HTTP HTTP(超文本传输协议)是一种工作在应用层的协议.主要用于网站,就是浏览器和服务器之间的数据传输. 小知识:什么是超文本传输协议 文本:是字符串.(能在utf8/gbk码表上找到合法字符) 超文本:不仅可以传输字符串,也可以传输图片,html等 富文本:word文档 2.HT…...

创建型模式 - 原型模式 (Prototype Pattern)

创建型模式 -原型模式 (Prototype Pattern) 它允许通过复制现有对象来创建新对象&#xff0c;而无需知道对象的具体创建细节。在 Java 中&#xff0c;可以通过实现 Cloneable 接口和重写 clone() 方法来实现原型模式。 有深、浅两种克隆 类实现 Cloneable 接口就可以深克隆如果…...

Android 8.0 (API 26) 对广播机制做了哪些变化

大部分隐式广播无法通过静态注册接收&#xff0c;除了以下白名单广播&#xff1a; ACTION_BOOT_COMPLETED ACTION_TIMEZONE_CHANGED ACTION_LOCALE_CHANGED ACTION_MY_PACKAGE_REPLACED ACTION_PACKAGE_ADDED ACTION_PACKAGE_REMOVED 需要以动态注册方案替换&#xff1a; cl…...

Unity汽车笔记

汽车的移动和转向 我们知道&#xff0c;汽车的前进后退是变速运动。按w&#xff0c;汽车开始加速&#xff0c;到最大速度后保持匀速&#xff0c;松开w&#xff0c;汽车受到阻力加速。如果按s减速&#xff0c;则以更大的加速度减速。后退反之。 按A/D时前轮偏转。只有前进后退…...

html中rel、href、src、url的区别

1.url url&#xff08;统一资源定位符&#xff09;&#xff1a;是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示&#xff0c;是互联网上标准资源的地址。 2.href href&#xff1a;Hypertext Reference的缩写。 意思是超文本引用。 3.rel rel&#xff1a;relatio…...

【idea问题排查技巧】

以下是针对 IDEA 中 日志打标(动态标记) 和 全链路追踪 功能的分步详解,结合具体场景和操作截图说明,帮助快速掌握实战技巧。 一、动态日志打标:不修改代码输出关键信息 1. 断点日志打印(非侵入式打标) 场景:在调试时,需要临时查看某个变量的值,但不想修改代码添加…...

SQL: DDL,DML,DCL,DTL,TCL,

Structured Query Language&#xff0c;结构化查询语言, 是一种用于管理和操作关系数据库的标准编程语言。 sql的分类 DQL&#xff08;Data Query Language&#xff09;&#xff1a;数据查询语言 DDL&#xff08;Data Definition Language&#xff09;&#xff1a;数据定义语…...

WordPress R+L Carrier Edition sql注入漏洞复现(CVE-2024-13481)(附脚本)

免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...