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

ChatGLM3 tool_registry.py 代码解析

ChatGLM3 tool_registry.py 代码解析

  • 0. 背景
  • 1. tool_registry.py

0. 背景

学习 ChatGLM3 的项目内容,过程中使用 AI 代码工具,对代码进行解释,帮助自己快速理解代码。这篇文章记录 ChatGLM3 tool_registry.py 的代码解析内容。

1. tool_registry.py

from copy import deepcopy
import inspect
from pprint import pformat
import traceback
from types import GenericAlias
from typing import get_origin, Annotated_TOOL_HOOKS = {}
_TOOL_DESCRIPTIONS = {}

这段代码定义了几个全局变量和导入了一些模块。让我来逐个解释:

  • from copy import deepcopy:从 copy 模块导入 deepcopy 函数,用于深拷贝对象。

  • import inspect:导入 inspect 模块,用于获取对象的信息。

  • from pprint import pformat:从 pprint 模块导入 pformat 函数,用于格式化打印对象。

  • import traceback:导入 traceback 模块,用于打印异常堆栈信息。

  • from types import GenericAlias:从 types 模块导入 GenericAlias 类,用于表示泛型类型。

  • from typing import get_origin, Annotated:从 typing 模块导入 get_origin 和 Annotated 函数,用于获取泛型类型的原始类型和注解信息。

  • _TOOL_HOOKS = {}:定义一个空的全局字典变量 _TOOL_HOOKS,用于存储工具的钩子函数。

  • _TOOL_DESCRIPTIONS = {}:定义一个空的全局字典变量 _TOOL_DESCRIPTIONS,用于存储工具的描述信息。

这段代码的作用可能是为后续的工具注册和存储钩子函数以及描述信息提供了一个全局的数据结构。

def register_tool(func: callable):tool_name = func.__name__tool_description = inspect.getdoc(func).strip()python_params = inspect.signature(func).parameterstool_params = []for name, param in python_params.items():annotation = param.annotationif annotation is inspect.Parameter.empty:raise TypeError(f"Parameter `{name}` missing type annotation")if get_origin(annotation) != Annotated:raise TypeError(f"Annotation type for `{name}` must be typing.Annotated")typ, (description, required) = annotation.__origin__, annotation.__metadata__typ: str = str(typ) if isinstance(typ, GenericAlias) else typ.__name__if not isinstance(description, str):raise TypeError(f"Description for `{name}` must be a string")if not isinstance(required, bool):raise TypeError(f"Required for `{name}` must be a bool")tool_params.append({"name": name,"description": description,"type": typ,"required": required})tool_def = {"name": tool_name,"description": tool_description,"params": tool_params}print("[registered tool] " + pformat(tool_def))_TOOL_HOOKS[tool_name] = func_TOOL_DESCRIPTIONS[tool_name] = tool_defreturn func

这段代码定义了一个名为 register_tool 的函数,该函数接受一个可调用对象 func 作为参数。

以下是代码的详细解析:

  • tool_name = func.name:获取传入函数 func 的名称,并将其赋值给变量 tool_name。
  • tool_description = inspect.getdoc(func).strip():使用 inspect.getdoc 函数获取传入函数 func 的文档字符串,并去除首尾的空白字符,将结果赋值给变量 tool_description。
  • python_params = inspect.signature(func).parameters:使用 - inspect.signature 函数获取传入函数 func 的参数签名,并将其参数信息保存在变量 python_params 中。
  • tool_params = []:创建一个空列表 tool_params,用于存储工具的参数信息。
  • for name, param in python_params.items()::遍历 python_params 中的每个参数项,其中 name 是参数名,param 是参数对象。
    • annotation = param.annotation:获取参数对象的注解,并将其赋值给变量 annotation。
    • if annotation is inspect.Parameter.empty::如果注解为空,则表示参数缺少类型注解,抛出 TypeError 异常。
    • if get_origin(annotation) != Annotated::如果注解类型不是 typing.Annotated,抛出 TypeError 异常。
    • typ, (description, required) = annotation.origin, annotation.metadata:从注解中获取类型和元数据信息,并将其分别赋值给 typ、description 和 required 变量。
    • typ: str = str(typ) if isinstance(typ, GenericAlias) else typ.name:将类型转换为字符串,如果类型是 GenericAlias(泛型类型),则将其转换为字符串形式,否则保留类型的名称。
    • if not isinstance(description, str)::如果描述不是字符串类型,抛出 TypeError 异常。
    • if not isinstance(required, bool)::如果 required 不是布尔类型,抛出 TypeError 异常。
    • tool_params.append({…}):将参数的名称、描述、类型和是否必需组成的字典添加到 tool_params 列表中。
  • tool_def = {…}:创建一个字典 tool_def,包含工具的名称、描述和参数信息。
  • print("[registered tool] " + pformat(tool_def)):打印注册的工具的定义,使用 pformat 函数格式化输出。
  • _TOOL_HOOKS[tool_name] = func:将函数 func 添加到全局字典变量 _TOOL_HOOKS 中,键为工具的名称。
  • _TOOL_DESCRIPTIONS[tool_name] = tool_def:将工具的定义添加到全局字典变量 _TOOL_DESCRIPTIONS 中,键为工具的名称。
  • return func:返回原始的函数 func。
    这段代码的作用是将传入的函数作为工具进行注册,并将工具的名称、描述和参数信息存储在全局字典变量中。注册的工具可以通过 _TOOL_HOOKS 全局字典变量进行调用。
def dispatch_tool(tool_name: str, tool_params: dict) -> str:if tool_name not in _TOOL_HOOKS:return f"Tool `{tool_name}` not found. Please use a provided tool."tool_call = _TOOL_HOOKS[tool_name]try:ret = tool_call(**tool_params)  except:ret = traceback.format_exc()return str(ret)def get_tools() -> dict:return deepcopy(_TOOL_DESCRIPTIONS)

这段代码定义了两个函数:dispatch_tool 和 get_tools。让我为你逐个解释:

函数 dispatch_tool(tool_name: str, tool_params: dict) -> str:

该函数接受两个参数 tool_name 和 tool_params,并返回一个字符串。

  • if tool_name not in _TOOL_HOOKS::检查传入的工具名称 tool_name 是否存在于全局字典变量 _TOOL_HOOKS 中。
    如果工具名称不存在,返回一个提示字符串,表示找不到该工具。
  • tool_call = _TOOL_HOOKS[tool_name]:从全局字典变量 _TOOL_HOOKS 中获取与工具名称对应的工具函数,并将其赋值给变量 tool_call。
  • try::尝试执行工具函数,并捕获可能的异常。
    • ret = tool_call(**tool_params):使用传入的参数 tool_params 调用工具函数,并将返回值赋值给变量 ret。这里使用 ** 运算符将 tool_params 字典解包为关键字参数传递给工具函数。
  • except::捕获可能的异常。
    • ret = traceback.format_exc():如果出现异常,将异常的堆栈信息格式化为字符串,并将其赋值给变量 ret。
  • return str(ret):返回结果,无论是工具函数的返回值还是异常堆栈信息,都将转换为字符串并返回。
    该函数的作用是根据传入的工具名称和参数调用对应的工具函数,并返回结果或异常信息的字符串表示。

函数 get_tools() -> dict:

该函数不接受任何参数,返回一个字典。

  • return deepcopy(_TOOL_DESCRIPTIONS):返回全局字典变量 _TOOL_DESCRIPTIONS 的深拷贝。
    该函数的作用是返回全局字典变量 _TOOL_DESCRIPTIONS 的副本,以提供工具的名称、描述和参数信息。

这两个函数一起提供了工具的调度和获取工具信息的功能。dispatch_tool 函数用于调用具体的工具函数,而 get_tools 函数用于获取所有已注册工具的描述信息。

deepcopy: deepcopy 是一个函数,用于创建一个对象的深拷贝。深拷贝是指创建一个新对象,将原始对象的所有元素递归地复制到新对象中,包括嵌套的对象。换句话说,它会创建一个原始对象的完全独立副本,而不仅仅是引用原始对象的内存地址。
深拷贝对于需要完全独立的副本的情况非常有用,尤其是在处理可变对象时。通过深拷贝,可以确保修改一个对象的副本不会影响到原始对象,因为它们是相互独立的。
例如,假设有一个包含嵌套列表和字典的对象 obj,如果直接对 obj 进行赋值操作,那么新对象将只是原始对象的引用,而不是副本。这意味着对新对象的修改也会反映到原始对象中。但是,如果使用 deepcopy 函数创建一个新对象 new_obj,那么 new_obj 将是 obj 的深拷贝副本,对 new_obj 的修改不会影响到 obj。

@register_tool
def random_number_generator(seed: Annotated[int, 'The random seed used by the generator', True], range: Annotated[tuple[int, int], 'The range of the generated numbers', True],
) -> int:"""Generates a random number x, s.t. range[0] <= x < range[1]"""if not isinstance(seed, int):raise TypeError("Seed must be an integer")if not isinstance(range, tuple):raise TypeError("Range must be a tuple")if not isinstance(range[0], int) or not isinstance(range[1], int):raise TypeError("Range must be a tuple of integers")import randomreturn random.Random(seed).randint(*range)

这段代码定义了一个名为 random_number_generator 的函数,并使用 @register_tool 装饰器将其注册为一个工具。

函数接受两个参数 seed 和 range,并返回一个整数。下面是对代码的详细解释:

  • @register_tool:@ 符号是装饰器语法,用于在函数定义之前修饰函数。@register_tool 表示将该函数注册为一个工具。具体工具注册的逻辑在你提供的代码中没有呈现,可以在其他地方找到。

  • def random_number_generator(seed: Annotated[int, ‘The random seed used by the generator’, True], range: Annotated[tuple[int, int], ‘The range of the generated numbers’, True]) -> int::这是函数的定义部分。函数名为 random_number_generator,接受两个参数 seed 和 range,并指定返回类型为整数。

  • “”" Generates a random number x, s.t. range[0] <= x < range[1] “”":这是函数的文档字符串(docstring),用于描述函数的功能。根据文档字符串的描述,该函数生成一个介于 range[0] 和 range[1] 之间的随机整数 x。

  • 参数验证部分:在函数体内部,对传入的参数进行验证,确保它们具有正确的类型和值。

    • if not isinstance(seed, int)::检查 seed 是否为整数类型,如果不是,则抛出 TypeError 异常,提示 “Seed must be an integer”。
    • if not isinstance(range, tuple)::检查 range 是否为元组类型,如果不是,则抛出 TypeError 异常,提示 “Range must be a tuple”。
    • if not isinstance(range[0], int) or not isinstance(range[1], int)::检查 range 的元素是否为整数类型,如果不是,则抛出 TypeError 异常,提示 “Range must be a tuple of integers”。
  • import random:导入 Python 标准库中的 random 模块,用于生成随机数。

  • return random.Random(seed).randint(*range):使用 random 模块生成一个随机整数,并将其作为函数的返回值。random.Random(seed) 创建了一个具有指定种子 seed 的随机数生成器对象,然后使用 randint(*range) 方法生成介于 range[0] 和 range[1] 之间的随机整数。

总之,这段代码定义了一个将参数验证和随机数生成结合在一起的函数。它使用装饰器将函数注册为一个工具,并在调用时生成指定范围内的随机整数。

@register_tool
def get_weather(city_name: Annotated[str, 'The name of the city to be queried', True],
) -> str:"""Get the current weather for `city_name`"""if not isinstance(city_name, str):raise TypeError("City name must be a string")key_selection = {"current_condition": ["temp_C", "FeelsLikeC", "humidity", "weatherDesc",  "observation_time"],}import requeststry:resp = requests.get(f"https://wttr.in/{city_name}?format=j1")resp.raise_for_status()resp = resp.json()ret = {k: {_v: resp[k][0][_v] for _v in v} for k, v in key_selection.items()}except:import tracebackret = "Error encountered while fetching weather data!\n" + traceback.format_exc() return str(ret)

这段代码定义了一个名为 get_weather 的函数,并使用 @register_tool 装饰器将其注册为一个工具。

函数接受一个参数 city_name,并返回一个字符串。下面是对代码的详细解释:

  • @register_tool:@ 符号是装饰器语法,用于在函数定义之前修饰函数。@register_tool 表示将该函数注册为一个工具。具体工具注册的逻辑在你提供的代码中没有呈现,可以在其他地方找到。

  • def get_weather(city_name: Annotated[str, ‘The name of the city to be queried’, True]) -> str::这是函数的定义部分。函数名为 get_weather,接受一个 city_name 参数,指定返回类型为字符串。

  • “”" Get the current weather for city_name “”":这是函数的文档字符串(docstring),用于描述函数的功能。根据文档字符串的描述,该函数用于获取指定城市的当前天气情况。

  • 参数验证部分:在函数体内部,对传入的参数进行验证,确保它们具有正确的类型和值。

    • if not isinstance(city_name, str)::检查 city_name 是否为字符串类型,如果不是,则抛出 TypeError 异常,提示 “City name must be a string”。
  • key_selection = {…}:定义了一个字典变量 key_selection,用于存储需要从 API 响应中提取的天气信息的键值选择。该字典的键代表不同的天气信息,而对应的值是一个列表,包含了该天气信息所对应的子键。

  • import requests:导入 Python 第三方库 requests,用于发送 HTTP 请求。

  • try::尝试执行一段代码,并捕获可能的异常。

    • resp = requests.get(f"https://wttr.in/{city_name}?format=j1"):使用 requests 发送一个 GET 请求,获取指定城市的天气数据。URL 中的 {city_name} 部分会被替换为实际的城市名称。
    • resp.raise_for_status():检查请求的状态码,如果是错误的状态码,将抛出一个异常。
    • resp = resp.json():将响应的 JSON 数据解析为 Python 字典,并将其赋值给 resp 变量。
    • ret = {…}:根据 key_selection 字典中的键值选择,从响应中提取相应的天气信息,存储在 ret 变量中。这里使用了字典推导式来生成结果。
  • except::捕获可能的异常。

    • import traceback:导入 Python 标准库中的 traceback 模块,用于获取异常的堆栈信息。
    • ret = “Error encountered while fetching weather data!\n” + traceback.format_exc():如果发生异常,将错误提示信息和堆栈信息拼接成一个字符串,并将其赋值给 ret 变量。
  • return str(ret):返回结果,将结果转换为字符串类型后返回。

总之,这段代码定义了一个用于获取指定城市天气的函数。它使用 requests 库发送 HTTP 请求获取天气数据,并从响应中提取指定的天气信息。如果发生任何异常,它会将错误提示信息和堆栈信息返回。

请注意,这段代码中的 @register_tool 装饰器和 requests 库是额外的依赖项,你可能需要在其他地方找到这些实现或库的定义。

完结!

相关文章:

ChatGLM3 tool_registry.py 代码解析

ChatGLM3 tool_registry.py 代码解析 0. 背景1. tool_registry.py 0. 背景 学习 ChatGLM3 的项目内容&#xff0c;过程中使用 AI 代码工具&#xff0c;对代码进行解释&#xff0c;帮助自己快速理解代码。这篇文章记录 ChatGLM3 tool_registry.py 的代码解析内容。 1. tool_re…...

js实现定时刷新,并设置定时器上限

定时器 在js中&#xff0c;有两种定时器&#xff1a; 倒计时定时器 倒计时定时器&#xff0c;也叫延时定时器或一次性定时器 功能&#xff1a;倒计时多长时间后执行某个动作 语法&#xff1a;setTimeout(function, timeout); 返回值&#xff1a;int类型&#xff0c;当前定时器…...

常用Linux命令

df -h #查看磁盘 kill -9 pid #强制关闭程序 ifconfig #查看网卡信息 last …...

【C++】获取指定点所在屏幕的尺寸

问题 多个显示器时&#xff0c;获取指定点所在的显示器的尺寸。 分析 之前整理过获取屏幕尺寸的方法&#xff1a;https://blog.csdn.net/m0_43605481/article/details/125024500多显示器时&#xff0c;需要用到GetSystemMetrics、EnumDisplayDevices、EnumDisplaySettings函…...

软文发布如何选择对应的媒体

企业做软文推广第一步&#xff0c;就是选择合适的媒体进行投放&#xff0c;然而许多企业不知道如何选择合适的媒体导致推广工作十分被动&#xff0c;无法取得效果&#xff0c;今天媒介盒子就来和大家分享&#xff0c;企业应该如何选择对应的媒体。 一、 媒体类型 根据软文类型…...

Django如何创建表关系,Django的请求声明周期流程图

【1】表与表之间的关系 一对一 左表的一条记录对应右表的一条记录&#xff0c;反之亦然 多对一 左表的一条记录对应右表的多条记录&#xff0c;反之不成立 多对多 左表的一条记录对应右表的多表记录&#xff0c;反之成立 【2】django中创建表关系 class Book(models.Model):t…...

微服务-我对Spring Clound的理解

官网&#xff1a;https://spring.io/projects/spring-cloud 官方说法&#xff1a;Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具&#xff08;例如配置管理、服务发现、熔断器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话…...

安防监控EasyCVR视频汇聚平台无法接入Ehome5.0是什么原因?该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放…...

机器学习——逻辑回归

目录 一、分类问题 监督学习的最主要类型 二分类 多分类 二、Sigmoid函数 三、逻辑回归求解 代价函数推导过程&#xff08;极大似然估计&#xff09;&#xff1a; 交叉熵损失函数 逻辑回归的代价函数 代价函数最小化——梯度下降&#xff1a; ​编辑 正则化 四、逻辑…...

自动驾驶学习笔记(七)——感知融合

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 感知融合 卡尔曼滤波 融合策略 实…...

【Java0基础学Java第八颗】 -- 继承与多态 -- 多态

8.继承与多态 8.2 多态8.2.1 多态的概念8.2.2 多态实现条件8.2.3 重写8.2.4 向上转型和向下转型8.2.5 向下转型8.2.6 多态的优缺点8.2.7 避免在构造方法中调用重写的方法 8.2 多态 8.2.1 多态的概念 通俗来说就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当…...

玩转ansible之参数调试和文件操作篇

更多IT技术文章&#xff0c;欢迎关注微信公众号“运维之美” 玩转ansible之参数调试和文件操作篇 01 剧本调试和帮助02 使用场景举例 上节我们学习了使用ansible进行软件安装&#xff0c;那么安装完软件后&#xff0c;就需要linux系统和软件配置修改了&#xff0c;对于linux主机…...

JVM虚拟机:垃圾回收器之Parallel Old(老年代)

本文重点 本文将学习老年代的另外一种垃圾回收器Parallel Old(PO)&#xff0c;这是一种用于老年代的并行化垃圾回收器&#xff0c;它使用标记整理算法进行垃圾回收。 历史 在1.6之前&#xff0c;新生代使用Parallel Scavenge只能搭配老年代的Serial Old收集器&#xff0c;而…...

Stream流的groupingBy

Stream流的groupingBy 简单使用 业务场景&#xff1a;现在有100个人&#xff0c;这些人都年龄分部在18-30岁之间。现要求把他们按照年龄进行分组 key&#xff1a;年龄 value&#xff1a;数据列表 public void listToMapGroup() {//这里假设通过listStreamService.list();方法…...

如何在不结束tcpdump的情况下复制完整的pcap

tcpdump正在运行的时候&#xff0c;他写入的pcap可能是不完整的&#xff0c;通常我们要结束掉tcpdump才能拿到完整的pcap&#xff0c;否则wireshark打开的时候会提示&#xff1a;The capture file appears to have been cut short in the middle of a packet。这可能是因为tcpd…...

maven POM文件总体配置说明

<project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd "> <!-- 父项目的坐…...

49.批处理命令(1/2)

目录 一批处理。 &#xff08;1&#xff09;批处理定义。 &#xff08;2&#xff09;常见命令。 &#xff08;2.1&#xff09;rem和:: &#xff08;2.2&#xff09;echo和。 &#xff08;2.3&#xff09;pause。 &#xff08;2.4&#xff09;errorlevel。 &#xff08;…...

react类式组件的生命周期和useEffect实现函数组件生命周期

概念 生命周期是一个组件丛创建,渲染,更新,卸载的过程,无论是vue还是react都具有这个设计概念,也是开发者必须熟练运用的,特别是业务开发,不同的生命周期做不同的事是很重要的. ....多说两句心得,本人是先接触vue的,无论是vue2还是vue3的生命周期,在理解和学习上都会比react更…...

ARM 基础学习记录 / 异常与GIC介绍

GIC概念 念课本&#xff08;以下内容都是针对"通用中断控制器&#xff08;GIC&#xff09;"而言&#xff0c;直接摘录的&#xff0c;有的地方可能不符人类的理解方式&#xff09;&#xff1a; 通用中断控制器&#xff08;GIC&#xff09;架构提供了严格的规范&…...

java压缩pdf体积,图片体积

pdf整体进行压缩,图片进行压缩 // 生成主证书的PDF路径 创建一个文件String pdfPath UploadDown.createFile(".pdf");outputStream new FileOutputStream(pdfPath);bufferedOutputStream new BufferedOutputStream(outputStream);writer PdfWriter.getInstance(…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

什么是EULA和DPA

文章目录 EULA&#xff08;End User License Agreement&#xff09;DPA&#xff08;Data Protection Agreement&#xff09;一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA&#xff08;End User License Agreement&#xff09; 定义&#xff1a; EULA即…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

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

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

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...

Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么&#xff1f;它的作用是什么&#xff1f; Spring框架的核心容器是IoC&#xff08;控制反转&#xff09;容器。它的主要作用是管理对…...