Wireshark自定义Lua插件

背景:

常见的抓包工具有tcpdump和wireshark,二者可基于网卡进行抓包:tcpdump用于Linux环境抓包,而wireshark用于windows环境。抓包后需借助包分析工具对数据进行解析,将不可读的二进制数转换为可读的数据结构。
wireshark不仅可以作为抓包工具,还可以作为包解析工具。Wireshark针对常见协议都提供了对应的解析插件,
如: TCP、UDP、HTTP、SIP等;同时提供了自定义插件机制,用户可以基于此解析自定义消息。至于插件,wireshark支持C语言插件和Lua插件,Lua作为脚本不需要编译,方便调试,速度相对C语言较慢。由于抓包时可以根据条件过滤,且一般数据包分析在本地进行,这部分性能优势相对于Lua脚本的方便性可以忽略。
因此,本文的主体内容是介绍如何在Wireshark中开发自定义插件解析消息。

1.插件配置方式

1.1 配置protobuf加载路径

根据Wireshark->Preferences->Protocols路径进入配置页面(Windows中路径为 “编辑->首选项->Protocols” ):
在这里插入图片描述
勾选Load .proto files on startup选项,然后点击Edit按钮开始配置。添加proto文件所在文件夹,勾选"load all files"选项。
经过上述配置,已经为wireshark指定了查找proto文件的路径,后续在lua脚本中可直接使用proto文件。

1.2 配置lua脚本路径

根据Wireshark->About Wireshark->Folders路径进入配置页面(windows下路径为: 帮助->关于->文件夹):
在这里插入图片描述
添加或者查看个人Lua插件的存放位置,后面开发的插件需要存放到这个路径下才会生效。添加或者修改lua插件后,需要重新加载lua插件:"分析->重新载入Lua插件"或者通过快捷键Ctrl+Shift+L.

1.3 Lua console调试工具

在"Tools->Lua console"页面可以编写和执行Lua脚本,可用于调试:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2T20zdx-1717753458062)(C:\Users\0216001379\AppData\Roaming\Typora\typora-user-images\1716602725265.png)]
说明:调试工具是开发Lua插件的关键,结合快捷键Ctrl+Shift+L,通过打印提示信息,可以快速定位和发现问题。

2.wireshark关于Lua API介绍

Lua语法请参考: Lua使用方式介绍

Lua API参考自: https://mika-s.github.io/wireshark/lua/dissector/2017/11/04/creating-a-wireshark-dissector-in-lua-1.html

这部分介绍wireshark为Lua脚本的API,以及根据如何使用这些API实现自定义Lua插件。介绍Lua API前,有必要对Wireshark页面进行介绍:
在这里插入图片描述

需要关注上图红色标注的区域,包括:column区、tree区、data区,后续API会操作这些区域。

2.1 定义协议

seong_protocol = Proto("seong",  "seong description")seong_protocol.dissector = function(buffer, pinfo, tree)local subtree = tree:add(seong_protocol, buffer(),"Seong Message Data"); 
endDissectorTable.get("tcp.port"):add(9003, seong_protocol)

将上述Lua插件注册到环境后,可使用自定义的seong协议过滤消息,Wireshark页面显示如下:
在这里插入图片描述
定义协议需要三个步骤:定义Proto协议对象,为Proto对象添加解码器方法,将Proto对象与对应的端口进行绑定。
[1] 定义协议对象

seong_protocol = Proto("seong",  "seong description")

Proto作为构造参数用于创建Proto对象,接收两个参数,协议名称和协议描述。

[2] 为协议对象添加解析器
解析器函数:

seong_protocol.dissector = function(buffer, pinfo, tree)local subtree = tree:add(seong_protocol, buffer(),"Seong Message Data"); 
end

函数包括三个入参:
(1) buffer为二进制消息数据,可以通过类似buffer(0,2)方式从消息中截取字节数组;
(2) pinfo为数据包的元数据对象,包括消息大小、源地址/目标地址、大小、时间戳等信息;
(3) tree为协议树节点对象,数据结构会被渲染在tree区。

tree.add方法:
解析器内部的tree:add(seong_protocol, buffer(),"Seong Message Data")功能是: 在tree区域添加一个子树(并返回子树的引用)。其中第一个参数是必选的,后面两个参数是可选的:
(1) 协议参数
tree区域中,每层协议对应一个子树,即每个子tree需要与指定的协议绑定,此时需要为seong协议创建一个子树:

local subtree = tree:add(seong_protocol, nil, nil);

后续通过操作subtree对象,为seong协议子树添加显示数据。

(2) 数据参数
当传递为nil和buffer或者buffer(0,2) 时,鼠标选中seong协议时,关联的data区域不同:
在这里插入图片描述
(3) 描述信息
当描述信息为nil时,wireshark会选择使用协议的描述信息展示。

[3] 将协议与端口绑定
将协议与端口绑定后,Wireshark会自动将该端口上的消息使用绑定的协议解析:

DissectorTable.get("tcp.port"):add(9003, seong_protocol)

此时,TCP协议的9003端口的消息使用seong插件解析。

2.2 修改Column区

在过滤窗口,通过seong过滤后,可以得到TCP-9003端口的消息,显示的Protocol协议仍未TCP,应该修改为seong. 在解析器内部添加语句pinfo.columns.protocol:set(seong_protocol.name),得到:

seong_protocol = Proto("seong",  "seong description")seong_protocol.dissector = function(buffer, pinfo, tree)local subtree = tree:add(seong_protocol, buffer(),"Seong Message Data");pinfo.columns.protocol:set(seong_protocol.name); 
endDissectorTable.get("tcp.port"):add(9003, seong_protocol)

Wireshark显示为:
在这里插入图片描述
消息的协议名称修改为了seong.
除了protocol外,还可以通过pinfo.columns对象修改columns区域的其他字段的内容, 如修改info消息:
pinfo.columns.info:set("此时充值VIP可观看");
在这里插入图片描述

2.3 修改Tree区

Tree区为重点区域,自定义插件的核心功能是为了在这个区域直观地展示消息的内容。解析器的重点职责是从二进制数据中解析消息,并将消息作为字段添加到tree上,从而在Tree区域展示。
以下结合两种方式,其中通过Proto对象的fields属性方式是官方文档的推荐方式;直接操作tree对象方式是个人探索所得,相对比较简单(可能有坑)。

2.3.1 Proto对象的fields属性方式

先给出案例:

seong_protocol = Proto("seong",  "seong description")message_length = ProtoField.int32("message_length", "Message-Length", base.DEC)
seong_protocol.fields = {message_length}seong_protocol.dissector = function(buffer, pinfo, tree)local subtree = tree:add(seong_protocol, buffer(),"Seong Message Data");pinfo.columns.protocol:set(seong_protocol.name);subtree:add(message_length, 123456)
endDissectorTable.get("tcp.port"):add(9003, seong_protocol)

与之前的lua脚本区域在于新增了Proto.fields相关的逻辑:
[1] 声明字段类型
message_length = ProtoField.int32(“message_length”, “Message-Length”, base.DEC)
创建一个属性,属性名称为message_length,描述为Message-Length(显示使用), 为十进制的整数。
[2] 字段添加到协议对象中

seong_protocol.fields = {message_length}

[3] 为message_length赋值,并添加到tree中

subtree:add(message_length, 123456)

此时, wireshark显示如下:
在这里插入图片描述

2.3.2 直接操作tree对象

通过subtree:add(字符串)方法直接将字符串设置到tree对象上:

seong_protocol = Proto("seong",  "seong description")seong_protocol.dissector = function(buffer, pinfo, tree)local subtree = tree:add(seong_protocol, buffer(),"Seong Message Data");pinfo.columns.protocol:set(seong_protocol.name);subtree:add("Message-Length: " .. 11223344)
endDissectorTable.get("tcp.port"):add(9003, seong_protocol)

在这里插入图片描述

2.3.3 简单案例

假设消息中前两个字节表示有效的数据长度:

seong_protocol = Proto("seong",  "seong description")seong_protocol.dissector = function(buffer, pinfo, tree)local subtree = tree:add(seong_protocol, buffer(),"Seong Message Data");pinfo.columns.protocol:set(seong_protocol.name);subtree:add("Message-Length: " .. buffer(0,2))
endDissectorTable.get("tcp.port"):add(9003, seong_protocol)

其中,buffer(0,2)从二进制消息中提取前两个字节; subtree:add方法调用时,可以关联data区域:
subtree:add("Message-Length: " .. buffer(0,2)) 修改为
subtree:add(buffer(0,2), "Message-Length: " .. buffer(0,2)):
显示如下:
在这里插入图片描述

3.案例

3.1 protobuf文件准备

Person.proto文件:

syntax = "proto2";option java_package = "com.seong";option java_outer_classname = "TestProtoMsg";message Person {required int32 id = 1;required string name = 2;required bool isMale = 3;repeated Address address = 4;
};message Address {required string country = 1;optional string location = 2;
};

编译后,生成com.seong.TestProtoMsg类,内部有Person和Address两个内部类,生成的Java类将在服务端和客户端程序中使用。然后将Person.proto文件放到1.1章节中配置的protobuf加载路径下。

3.2 Java服务端和客户端单例

使用Netty构建一个服务端(监听端口为9999)与客户端, 二者之间通过TCP-Protobuf通信,消息格式如下:
在这里插入图片描述
首部固定为AAAA(2字节),消息大类为BB(1字节), 消息子类为CC(1字节),消息长度表示PB消息体的长度(2字节),PB消息内容为5.1中Person.proto文件的protobuf消息。

关于Netty相关代码这里不进行介绍,请参考IO系列-netty相关的文章。

客户端:

(1) 客户端Netty模板代码:

public class Application {public static void main(String[] args) throws Exception {new Application().start();}public void start() throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new PersonProtoBufEncoder());}});ChannelFuture f = b.connect("localhost", 9999).sync();f.channel().writeAndFlush(buildPersonMsg());f.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}
}

(2) 构造消息: 根据PB定义构造案例消息

private TestProtoMsg.Person buildPersonMsg() {TestProtoMsg.Person.Builder personBuilder = TestProtoMsg.Person.newBuilder();personBuilder.setId(1960001001);personBuilder.setName("ue001");personBuilder.setIsMale(false);TestProtoMsg.Address.Builder addressBuilder = TestProtoMsg.Address.newBuilder();addressBuilder.setCountry("zh-CN");addressBuilder.setLocation("NanJing");personBuilder.addAddress(addressBuilder.build());return personBuilder.setId(1).build();
}

(3) Protobuf编码器:将TestProtoMsg.Person对象编码为二进制数据,然后发送给服务端

public class PersonProtoBufEncoder extends MessageToByteEncoder<TestProtoMsg.Person> {private static final int TYPE = 4;private static final int LENGTH = 4;@Overrideprotected void encode(ChannelHandlerContext ctx, TestProtoMsg.Person person, ByteBuf byteBuf) {byte[] playLoadBytes = person.toByteArray();int playLoadLen = playLoadBytes.length;ByteBuf msgBuffer = Unpooled.buffer(TYPE + LENGTH + playLoadLen);msgBuffer.writeBytes(new byte[] {(byte)0xAA, (byte)0xAA});msgBuffer.writeBytes(new byte[] {(byte)0xBB, (byte)0xCC});msgBuffer.writeInt(playLoadLen);msgBuffer.writeBytes(playLoadBytes);byteBuf.writeBytes(msgBuffer);}
}

服务端:

(1) 服务端Netty模板代码:

public class Application {public static void main(String[] args) throws Exception {new Application().start(9999);}public void start(int port) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) {ch.pipeline().addLast(new PersonProtoBufDecoder());ch.pipeline().addLast(new PersonProtoServerHandler());}}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);ChannelFuture f = b.bind(port).sync();f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}
}

(2) 解码器: 将来自客户端的二进制数据解码为TestProtoMsg.Person对象

public class PersonProtoBufDecoder extends ByteToMessageDecoder {private static final int TYPE_HEAD = 4;private static final int LENGTH_HEAD = 4;private static final int HEAD_LEN = TYPE_HEAD + LENGTH_HEAD;@Overrideprotected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {byte[] msgBytes = new byte[byteBuf.readableBytes()];byteBuf.readBytes(msgBytes);int msgLen = msgBytes.length;if (msgLen <= HEAD_LEN) {return;}byte[] bodyMsg = new byte[msgLen - HEAD_LEN];System.arraycopy(msgBytes, HEAD_LEN, bodyMsg, 0, msgLen - HEAD_LEN);TestProtoMsg.Person person = TestProtoMsg.Person.parseFrom(bodyMsg);list.add(person);}
}

(3) 解码后的消息处理: 打印TestProtoMsg.Person对象

@Slf4j
public class PersonProtoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {if (!(msg instanceof TestProtoMsg.Person)) {ctx.fireChannelRead(msg);return;}LOGGER.info("Receive from client, msg is {}.", msg);}
}

运行结果如下所示:

17:17:42.874 [nioEventLoopGroup-3-1] INFO com.seong.PersonProtoServerHandler - Receive from client, msg is id: 1
name: "ue001"
isMale: false
address {country: "zh-CN"location: "NanJing"
}
.

3.3 抓包分析

通过wireshark或者tcpdump可进行抓包,这里对端口进行过滤(9999):
在这里插入图片描述
可以看到客户端与服务端的通信数据包已被获取,为二进制数据,没有可读性。

3.4 Lua脚本定义协议

-- 自定义协议:Proto构造函数有两个参数:名称和描述
seong_protocol = Proto("seong",  "seong Message")
-- 添加一个字段,用于在数据树中显示
message_length = ProtoField.int32("seong.message_length", "PB-Message-Length", base.DEC)
seong_protocol.fields = {message_length}-- 自定义协议的解析器
seong_protocol.dissector = function(buffer, pinfo, tree)-- 消息长度为0,直接返回local length = buffer:len();if length == 0 then return end;-- 添加子树,显示为Seong Message Datalocal subtree = tree:add(seong_protocol, buffer(),"Seong Message Data"); -- 消息树中添加PB-Message-Length信息local msgLen = buffer(4,4):uint()subtree:add(message_length, msgLen)-- 消息树中添加HEAD,Main-Type,Sub-Type数据local headFlag = "" .. buffer(0,2)local mainType = "" .. buffer(2,1)local subType = "" .. buffer(3,1)subtree:add(buffer(0,2),"HEAD: " .. headFlag)subtree:add(buffer(2,1),"Main-Type: " .. mainType)subtree:add(buffer(3,1),"Sub-Type: " .. subType)-- 调用wireshark内置的protobuf解析器sipProtoType = "Person";pinfo.private["pb_msg_type"] = "message," .. sipProtoTypelocal protobuf_dissector = Dissector.get("protobuf");local result = pcall(Dissector.call, protobuf_dissector, buffer(8, msgLen):tvb(), pinfo, subtree)pinfo.columns.protocol:set(seong_protocol.name)
end--注册协议到指定的端口
local tcp_port = DissectorTable.get("tcp.port"):add(9999, seong_protocol)
3.5 查看协议

在这里插入图片描述
此时二进制数据已经通过树区域进行了展示, 与服务端解码后的消息保持一致。

3.6 扩展

本文中涉及的protobuf文件只有一个,实际上系统间的消息类型数以百计,因此需要对上述Lua脚本进行扩展以具备更好的通用性。
注意到定义消息时,添加了消息大类和消息子类两个冗余字段,可通过这两个字段与protobuf之间建立映射关系,即这两个消息确定了消息类型和解码方式。

local function matchedProtoType(mainType, subType)print("mainType:" .. mainType .. "subType:" .. subType)if mainType == "bb" thenif subType == "cc" thenreturn "Person";endelsereturn nilendreturn nil
end

定义一个函数,根据mainType和subType返回protobuf消息类型,相应地,Lua脚本中解析器的定义进行如下修改(将硬编码的Person类型改为通过matchedProtoType获取):

idslds_protocol.dissector = function(buffer, pinfo, tree)-- ...--sipProtoType = "Person";sipProtoType = matchedProtoType(mainType,subType)-- ...
end

本文主要介绍如何在wireshark中介绍自定义Lua插件,因此扩展这一部分不进行详细描述。后续在IO系列-Netty应用相关的文章中将介绍一个通过Netty实现子网穿越的案例;之后结合该案例对Lua插件的应用进行完整的阐述。

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

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

相关文章

element-plus的el-text组件(文本组件)的介绍和使用

el-text&#xff08;适合文本操作的组件&#xff09; 设置文本type,如default,primary,success,info,warning,danger超出容器尺寸自动省略&#xff0c;tuncated属性设置size属性控制文本大小&#xff0c;有large,default,small设置tag属性&#xff0c;值为html5标签名&#xf…

Nagios的安装和使用

*实验* *nagios安装和使用* Nagios 是一个监视系统运行状态和网络信息的监视系统。Nagios 能监视所指定的本地或远程主机以及服务&#xff0c;同时提供异常通知功能等. Nagios 可运行在 Linux/Unix 平台之上&#xff0c;同时提供一个可选的基于浏览器的 WEB 界面以方便系统管…

【创作活动】面对层出不穷的AI大模型产品我们应该怎么选择?

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Vue CLI 4与项目构建实战指南

title: Vue CLI 4与项目构建实战指南 date: 2024/6/9 updated: 2024/6/9 excerpt: 这篇文章介绍了如何使用Vue CLI优化项目构建配置&#xff0c;提高开发效率&#xff0c;涉及配置管理、项目部署策略、插件系统定制以及Webpack和TypeScript的深度集成技巧。 categories: 前端…

五、身份与访问管理—身份管理和访问控制管理(CISSP)

目录 1.身份管理 1.1 目录技术 1.2 单点登录 1.2.1 Kerberos认证 1.2.2 SESAME认证 1.2.3 KryptoKnight认证 1.3 联合身份管理 1.3.1 SAML安全断言标记语言 1.3.2 标记语言 1.3.3 OpenID 1.3.4 OAuth 1.3.5 OIDC(OpenID Connect) 2.身份即服务(IDaaS) 2.1 AA…

Rocky linux 搭建DNS主从服务器+keepalived实现高可用

接上两篇文章&#xff0c;这篇文章跟上两篇没有直接关系。 第一篇&#xff1a;linux rocky 搭建DNS服务和禁止AD域控DNS&#xff0c;做到独立DNS并加域_linux 域控-CSDN博客文章浏览阅读519次&#xff0c;点赞20次&#xff0c;收藏10次。使用linux rocky 搭建DNS服务&#xff…

【个人博客搭建】(22)申请QQ开发者

这里我们要引入的一个概念是OAuth - OAuth 2.0是一个行业标准的授权协议&#xff0c;用于处理用户数据访问和分享的安全问题。它允许用户将他们对某些服务的访问权限授权给第三方应用&#xff0c;而无需分享他们的用户名和密码。以下是对OAuth 2.0的介绍&#xff1a; 基本概念 …

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:中国舰船研究院

中国舰船研究院又称中国船舶重工集团公司第七研究院&#xff0c;隶属于中国船舶重工集团公司&#xff0c;是专门从事舰船研究、设计、开发的科学技术研究机构&#xff0c;是中国船舶重工集团公司的军品技术研究中心、科技开发中心&#xff1b;主要从事舰船武器装备发展战略研究…

PDF编辑与转换的终极工具智能PDF处理Acrobat Pro DC

Acrobat Pro DC 2023是一款功能全面的PDF编辑管理软件&#xff0c;支持创建、编辑、转换、签署和共享PDF文件。它具备OCR技术&#xff0c;可将扫描文档转换为可编辑文本&#xff0c;同时提供智能PDF处理技术&#xff0c;确保文件完整性和可读性。此外&#xff0c;软件还支持电子…

竞拍商城系统源码后端PHP+前端UNIAPP

下载地址&#xff1a;竞拍商城系统源码后端PHP前端UNIAPP

手把手教你入门vue+springboot开发(二)

文章目录 前言一、开发环境搭建二、创建项目三、编写代码1.插件下载2.编写代码 四、测试验证总结 前言 前面我们构建了一个查询用户信息的简单springboot后端demo&#xff0c;今天我们先搭建VSCodevue的开发环境&#xff0c;然后在这个开发环境上构建一个demo&#xff0c;连接…

肠道病原体感染定植的三个关键角色——炎症、营养与共生菌群

谷禾健康 日常生活中大部分人都可能碰到过食用了不卫生或过期变质食物以及水源后&#xff0c;出现过恶心、腹痛&#xff0c;并伴有腹泻、食欲不振、呕吐等症状&#xff1f;如果有就要当心&#xff0c;这可能是病原体感染引起的胃肠道炎症。 但是&#xff0c;即使食用了同样食物…

mysql中 redo日志(下)

大家好。上篇文章我们介绍了什么是redo日志以及redo日志的写入过程。建议没看过上篇文章的同学先看一下上一篇文章&#xff0c;今天我们继续来说一说redo日志。 一、redo日志文件 1. redo日志刷盘时机 我们知道mtr运行过程中产生的一组redo日志在mtr结束时会被复制到log buf…

从VS Code源码看清晰代码之美

VS Code的产品做的很优秀&#xff0c;其源码也质量颇高&#xff0c;清晰、整洁、富有美感。 下面是 src\vs\workbench\common\notifications.ts 文件中的两段代码&#xff0c;大家感受一下&#xff1a; get sticky(): boolean {if (this._sticky) {return true; // explicitl…

冲刺面试加油

1、HTML语义化&#xff1f; 对于开发者而言&#xff0c;语义化标签有着更好的页面结构&#xff0c;有利于代码的开发编写和后期的维护。 对于用户而言&#xff0c;当网络卡顿时有良好的页面结构&#xff0c;有利于增加用户的体验。 对于爬虫来说&#xff0c;有利于搜索引擎的…

所谓自律,就是去对抗那些廉价的快乐

所谓自律&#xff0c;就是去对抗那些廉价的快乐 以下文章来源于洞见 &#xff0c;作者洞见 导语 打败内心那只及时享乐的猴子。 董宇辉说过这样一句话&#xff1a;“廉价的快乐是直接给你想要的东西&#xff0c;高等的快乐则会给你设置重重阻碍。” 廉价的快乐&#xff0c;就…

Huggingface的Transformer库经验总结

文章目录 transformerstransformers.Trainertrain()tr_loss self.training_step(model, inputs)dagn的forward中逐步计算损失函数 transformers transformers.Trainer class Trainer:#这段代码根据训练数据集的类型和硬件环境&#xff0c;选择适当的采样器来处理数据集。def…

【免费Web系列】大家好 ,今天是Web课程的第十七天点赞收藏关注,持续更新作品 !

这是Web第一天的课程大家可以传送过去学习 http://t.csdnimg.cn/K547r SpingBoot原理 在前面十多天的课程当中&#xff0c;我们学习的都是web开发的技术使用&#xff0c;都是面向应用层面的&#xff0c;我们学会了怎么样去用。而我们今天所要学习的是web后端开发的最后一个篇…

冯喜运:6.7晚间黄金原油行情分析及操作建议

【黄金消息面分析】&#xff1a;周四(6月6日)纽市尾盘&#xff0c;现货黄金盘中报2373.15美元/盎司&#xff0c;涨18.16美元或0.77%。如果金价反弹至上周高点2364上方&#xff0c;将引发一周看涨反转。日收盘价高于该价格水平将确认突破。一旦突破得到确认&#xff0c;金价进一…

Vue3【十一】08使用toRefs和toRef

08使用toRefs和toRef toRefs()函数将person对象中的name和age属性转换为响应式引用&#xff0c;并返回一个对象&#xff0c;对象中的name和age属性都是响应式引用&#xff0c;具有响应式功能。 toRef()函数将person对象中的name属性转换为响应式引用&#xff0c;并返回一个响应…