初次体验Tauri和Sycamore (2)

原创作者:庄晓立(LIIGO)
原创时间:2025年2月8日(首次发布时间)
原创链接:https://blog.csdn.net/liigo/article/details/145520637
版权所有,转载请注明出处。
关键词:Sycamore, Tauri, Dioxus, Leptos, Rust, WebAssembly, Reactive, JSX, React, Web

tauri-splash

前言

Tauri 2.0发布于2024年10月2日,Sycamore 0.9发布于2024年11月1日。二者在近期双双发布重大版本升级,是我(LIIGO)这次想体验他们的主要动机。Tauri自2022年发布v1.0之后就早已火出天际,而Sycamore自2022发布v0.8之后沉寂了两年之久,如今各自凤凰涅槃,他们的组合体会擦出怎样的火花?

这是上一篇体验Tauri 2.0的姊妹篇。本文重点是体验Sycamore 0.9。

关于Sycamore

Sycamore, a library that makes it effortless to write performant user interfaces using the power of fine-grained reactivity. Sycamore uses open web standards such as WebAssembly to run your Rust code on the web, an environment that has been traditionally dominated by JavaScript.

Sycamore库提供细粒度响应式能力,可轻松编写高性能UI应用。Sycamore使用WEB标准技术WebAssembly等,将你的Rust代码运行在WEB中(传统上此类应用由JavaScript主导)。

Sycamore使用类似于JSX的声明式DSL语言描述UI,并提供组件、事件、路由、双向数据绑定等功能。

Sycamore和Tauri的关系

Tauri负责整个GUI应用框架,Sycamore在框架中负责前端UI渲染。Sycamore也可以脱离Tauri独立工作,用于开发基于WASM的WEB前端应用。

创建App

本节内容主要摘抄自同系列文章第一篇《初次体验Tauri和Sycamore (1)》。

Tauri使用两个命令行工具 (create-tauri-app, tauri-cli) 创建和编译打包App。首先要安装这两个CLI:

cargo install create-tauri-app --locked
cargo install tauri-cli --version "^2.0.0" --locked

create-tauri-app, tauri-cli 从Rust源码编译(cargo install)都相当耗时(均依赖数百个crates)。我还是建议自行下载编译后版本放到cargo bin目录。我给他们提了建议,今后会推荐使用 cargo binstall 下载编译好的二进制CLI,而不是使用cargo install从源码开始编译。

这两个CLI也有都对应的npm包:create-tauri-app, @tauri-apps/cli,二者都是间接调用Rust编译好的可执行文件。供TS/JS前端使用。

执行如下命令开始创建Tauri app:cargo create-tauri-app。CLI会逐步引导你输入或选择如下信息:

  • 项目名称(Project name),默认是"tauri-app"
  • Identifier 默认是"com.tauri-app.app"
  • 前端语言,可选 Rust, TS/JS, .Net
  • UI模板,视前端语言而定
    • Rust UI模板:可选 Vanilla, Yew, Leptos, Sycamore, Dioxus
    • TS/JS UI模板:可选 Vanilla, Vue, Svelte, React, Solid, Angular, Preact
    • .Net UI模板:可选 Blazor

选TS/JS的UI模板前还需要选择包管理器:pnpm, yarn, npm, deno, bun

因为这次我(Liigo)想体验Tauri+Sycamore,因而前端语言选Rust,UI模板选Sycamore。

目录结构

Tauri+Sycamore App目录结构:

├─ public/
├─ src/
│  ├─ app.rs
│  └─ main.crs
├─ src-tauri/
│  ├─ ...
│  ├─ Cargo.toml
│  └─ tauri.conf.json
├─ .gitignore
├─ .taurignore
├─ Cargo.toml
├─ index.html
├─ README.md
├─ styles.css
└─ Trunk.toml

Tauri源码目录对前端和后端代码进行了隔离。后端代码使用src-tauri/子目录;前端代码使用除此之外的其他文件和子目录。

编译打包

本节内容主要摘抄自同系列文章第一篇《初次体验Tauri和Sycamore (1)》。

开发版

cargo tauri dev

编译完成后自动启动App,弹出GUI主窗口。允许开发者在App运行过程中修改前端源代码,Tauri(或者说Trunk)会自动编译,并刷新App窗口内容,但是App并不会中途退出或重启(原理:Trunk通过WebSocket向开发版App推送重新加载UI的指令;Dioxus虽然没用Trunk但也实现了类似机制)。

它会检查Trunk是否存在,不存在的话会自动下载源码并编译。但是在Windows下编译Trunk很可能会碰到如下问题(间接依赖openssl开发者库):

  It looks like you're compiling for MSVC but we couldn't detect an OpenSSLinstallation. If there isn't one installed then you can try the rust-opensslREADME for more information about how to download precompiled binaries ofOpenSSL:https://github.com/sfackler/rust-openssl#windowsnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...
error: failed to compile `trunk v0.21.2`, intermediate artifacts can be found at `E:\tmp\RUST_DIR\CARGO_TARGET_DIR`.
To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.

我找到的解决办法是,去github的trunk官方仓库下载编译好的trunk.exe,丢进cargo bin目录即可(或任意PATH目录均可)。

如果cargo tauri dev过程中看到如下提示时只需耐心等待:

Warn Waiting for your frontend dev server to start on http://localhost:1420/…

这是因为Trunk先启动了(WEB服务监听1420端口),等待APP主动连接。但是编译App需要时间,等它编译完并启动后才能连上。

通过App窗口右键菜单"检查"可以打开devtools。在App运行过程中,还可以在浏览器中打开 http://localhost:1420/ ,网页外观和功能跟App窗口是一样的(可视为App的另一个实例)。

发行版

cargo tauri build

编译App并打包为安装包。

如果编译过程中提示正在下载Wix但失败(Github国内连接不稳定):

Downloading https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip

你可以通过其他方法手动下载此连接,解压到如下目录:C:\Users\liigo\AppData\Local\tauri\WixTools314\(里面有一堆exe等文件)。

此方法是我(LIIGO)从 Tauri仓库源码 里扒出来的。实证管用。

同理,如果NSIS也下载不了,可以用类似的办法手动下载解压到目录C:\Users\liigo\AppData\Local\tauri\NSIS。但是我没用这个方法。因为我觉得,既然已经有Wix用来生成MSI安装包,就没必要再下载NSIS用来生成另一种安装包。我研究了一下,将配置文件tauri.conf.json里面的bundle.targets改为"msi"(原来是"all")即可禁用NSIS等。

文件大小

Tauri+Sycamore app发行版编译后是一个可独立运行的图形用户界面(GUI)exe,其内部整合了wasm/css/图片等文件,没有其他外部依赖。exe文件大小是10.3MB,对应的安装包msi文件大小是3.6MB(安装后也只有那个exe和一个用于卸载的快捷方式文件(指向系统文件msiexec.exe /x))。Sycamore生成的wasm文件大小为750KB(已包含在exe中)。App启动时有大约一两秒的窗口白屏。

作为对比,再看一下Tauri+Dioxus app的数据:exe大小10.6MB,msi大小3.9MB,wasm文件大小为1.3MB(debug版33MB或25MB),也有一两秒的启动白屏。大同小异吧。我(LIIGO)暂且认为这是Tauri App (Hello world)的平均水平。

这样的文件大小应该很香吧。最起码比Electron app香多了。

1MB的wasm文件,用在普通网站上,网络传输加载延迟是一个较大的负担,但是对Tauri app这种桌面应用而言,就是本地加载啊,性能没得说。况且Tauri还应用了"localhost free"技术,直接注入Webview,连本地WEB传输步骤也省了。

Instead of relying on localhost server that expose your frontend to all other processes we use native webview apis to inject the assets right before the webview requests hit the network stack. – FabianLars

资源占用

Tauri+Sycamore app发行版启动后,内部加载3到6个Webview2进程,连同exe合计占用内存60到90MB。

无操作时CPU占用率为0%;在app窗口上移动鼠标时,CPU占用率逐步上升到10%甚至更多。这个问题是不是需要改善呀。

20241230 LIIGO补记:在VsCode中打开Tauri+Sycamore app项目源码,VsCode大概占用2.5GB内存甚至更多。

Tauri+Dioxus app的表现与之类似。

前端代码全览

以下是Tauri CLI生成的前端核心文件src\app.rs全部代码,先整体感受一下Sycamore。

其主体代码定义了一个App组件(App函数)。App组件内部负责描述UI、管理状态、处理事件。

use serde::{Deserialize, Serialize};
use sycamore::futures::spawn_local_scoped;
use sycamore::prelude::*;
use sycamore::web::events::SubmitEvent;
use wasm_bindgen::prelude::*;#[wasm_bindgen]
extern "C" {#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])]async fn invoke(cmd: &str, args: JsValue) -> JsValue;
}#[derive(Serialize, Deserialize)]
struct GreetArgs<'a> {name: &'a str,
}#[component]
pub fn App() -> View {let name = create_signal(String::new());let greet_msg = create_signal(String::new());let greet = move |e: SubmitEvent| {e.prevent_default();spawn_local_scoped(async move {// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/let args = serde_wasm_bindgen::to_value(&GreetArgs {name: &name.get_clone()}).unwrap();let new_msg = invoke("greet", args).await;greet_msg.set(new_msg.as_string().unwrap());})};view! {main(class="container") {h1 {"Welcome to Tauri + Sycamore"}div(class="row") {a(href="https://tauri.app", target="_blank") {img(src="public/tauri.svg", class="logo tauri", alt="Tauri logo")}a(href="https://sycamore.dev", target="_blank") {img(src="public/sycamore.svg", class="logo sycamore", alt="Sycamore logo")}}p {"Click on the Tauri and Sycamore logos to learn more."}form(class="row", on:submit=greet) {input(id="greet-input", bind:value=name, placeholder="Enter a name...")button(r#type="submit") {"Greet"}}p {(greet_msg)}}}
}

声明式UI

Sycamore使用view!()宏描述UI,大致类似于React系的JSX语言。

语法上相对比较陌生。虽然本质上还是写HTML,但并未直接采用HTML或JSX语法,也未直接采用Rust语法,它最终呈现给我们的是一种糅合了函数调用和花括号子块的形式(form(attr1, attr2) { sub-nodes })。内部支持///* */注释。

view! {h1 {"Welcome to Tauri + Sycamore"}form(class="row", on:submit=greet) {input(id="greet-input", bind:value=name, placeholder="Enter a name...")button(r#type="submit") {"Greet"}}p {(greet_msg)}
}

在前端声明式UI语法上,不同的作者有不同的喜好倾向。LIIGO本人的感受是,Leptos更像JSX,Dioxus更像Rust,Sycamore则介于二者中间。主要区别是风格口味,暂且不谈孰好孰坏吧。总之它们都可以统一归结为“类JSX”或者“神似JSX”学派。

组件

在Sycamore中创建定义组件是非常简单的。组件有几个特征:普通的函数,添加#[component]标注,返回值类型固定为View,函数体主体是view!宏调用,里面是类JSX语法。组件函数内部view!前面,可以定义状态变量,可以写Rust代码;view!内部也可以嵌入Rust表达式;参见后文状态管理一节。

#[component]
pub fn Hello() -> View {view! {div {"hello"}}
}

我照猫画虎把前面app.rs里面的一部分UI提取为一个独立组件,welcome组件:

#[component]
fn welcome() -> View {view! {h1 {"Welcome to Tauri + Sycamore"}div(class="row") {a(href="https://tauri.app", target="_blank") {img(src="public/tauri.svg", class="logo tauri", alt="Tauri logo")}a(href="https://sycamore.dev", target="_blank") {img(src="public/sycamore.svg", class="logo sycamore", alt="Sycamore logo")}}p {"Click on the Tauri and Sycamore logos to learn more."}}
}

调用端语法示例:

#[component]
pub fn App() -> View {view! {self::welcome {}}
}

welcome前面必须加上self::前缀,否则编译报错:

error[E0425]: cannot find function welcome in module sycamore::rt::tags

看样子它固定去sycamore::rt::tagsmodule里加载组件,无视welcome就在当前module里。希望后续改进。


组件当然也可以有属性(Attributes),附官方示例定义代码和调用代码。

#[component(inline_props)]
fn Button(// `html` means that we are spreading onto an HTML element.// The other possible value is `svg`.//// `button` means that we are spreading onto a `<button>` element.#[prop(attributes(html, button))]attributes: Attributes,// We can still accept children.children: Children,// We can still accept other props besides `attributes`.other_prop: i32,
) -> View {view! {// The spread (`..xyz`) syntax applies all the attributes onto the element.button(..attributes)}
}
view! {Button(// Pass as many HTML attributes/events as you want.id="my-button",class="btn btn-primary",on:click=|_| {}// You can still pass in regular props as well.other_prop=123,) {// Children still gets passed into the `children` prop."Click me"}
}

状态管理

使用create_signal等函数创建状态对象,例如:

let name = create_signal(String::new());
let greet_msg = create_signal(String::new());

显示状态对象的值(小括号括住):

view! {p {(greet_msg)}
}

文本内插值:"ok " (greet_msg) " yes"。状态值必须写在常量文本双引号外面,严格来说是伪内插值;Leptos也类似(小括号改为花括号);Dioxus支持"ok {greet_msg} yes"真内插值。

更新状态对象的值(Signal<T>::set):

greet_msg.set(new_value);

双向绑定到组件属性(属性value⇄状态name),语法:bind:属性=状态,示例:

view! {input(id="greet-input", bind:value=name)
}

事件

以下示例代码处理FORM组件的submit事件,其中事件处理函数greet是一个闭包函数(有一个event参数,类型取决于具体事件):

let greet = move |e: SubmitEvent| {// ...
};view! {form(class="row", on:submit=greet) {// ...}
}

事件处理函数通常是被写成组件函数内部的闭包函数形式,这是因为事件处理函数内往往需要访问状态对象(Signal<T>),而状态对象往往是组件函数内部的局部变量,在组件函数内部定义的闭包函数访问组件函数内的局部变量是很方便的(自动捕获闭包外部变量)。

但是这种形式把逻辑代码和UI代码放在一个组件函数里,很容易导致大函数缝合怪,一大坨事件处理代码 + 一大坨UI描述代码(view!{}),互相喧宾夺主。我感觉组件函数的主体应该是view!{}(加上少量状态对象定义代码),理想情况下应该把事件处理代码隔离出去。

事件处理代码当然也可以写成独立函数的形式(独立于组件函数),我(LIIGO)专门做了一些尝试并且也成功了(不知道能不能作为最佳实践)。首先要解决Signal对象不在当前作用域的问题,需要通过参数传递Signal,可是有了Signal参数这函数就不可能符合事件处理函数的原型(有且只有一个Event参数)。我的做法是让这个函数执行后再返回符合事件处理函数原型的闭包函数。代码如下,mygreet()为独立函数,调用mygreet()后返回事件处理函数:

#[component]
pub fn App() -> View {let name = create_signal(String::from("LIIGO"));let greet_msg = create_signal(String::new());view! {form(class="row", on:submit=mygreet(name, greet_msg)) {// ...}}
}fn mygreet(name: Signal<String>, greet_msg: Signal<String>) -> impl Fn(SubmitEvent) {move |e| {let new_msg = invoke("greet", args).await;greet_msg.set(new_msg.as_string().unwrap());}
}

整改后的app.rs

以下是我整改后的app.rs文件全部代码,主要是拆解、抽象、隔离,但保持原有功能不变。

use serde::{Deserialize, Serialize};
use sycamore::futures::spawn_local_scoped;
use sycamore::prelude::*;
use sycamore::web::events::SubmitEvent;
use wasm_bindgen::prelude::*;#[wasm_bindgen]
extern "C" {#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])]async fn invoke(cmd: &str, args: JsValue) -> JsValue;
}#[derive(Serialize, Deserialize)]
struct GreetArgs<'a> {name: &'a str,
}#[component]
pub fn App() -> View {let name = create_signal(String::from("LIIGO"));let greet_msg = create_signal(String::new());view! {main(class="container") {self::welcome {}form(class="row", on:submit=mygreet(name, greet_msg)) {input(id="greet-input", bind:value=name, placeholder="Enter a name...")button(r#type="submit") {"Greet"}}p {(greet_msg)}}}
}fn mygreet(name: Signal<String>, greet_msg: Signal<String>) -> impl Fn(SubmitEvent) {move |e| {print!("{}", name.get_clone());e.prevent_default();spawn_local_scoped(async move {// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/let args = serde_wasm_bindgen::to_value(&GreetArgs {name: &name.get_clone()}).unwrap();let new_msg = invoke("greet", args).await;greet_msg.set(new_msg.as_string().unwrap());})}
}#[component]
fn welcome() -> View {view! {h1 {"Welcome to Tauri + Sycamore"}div(class="row") {a(href="https://tauri.app", target="_blank") {img(src="public/tauri.svg", class="logo tauri", alt="Tauri logo")}a(href="https://sycamore.dev", target="_blank") {img(src="public/sycamore.svg", class="logo sycamore", alt="Sycamore logo")}}p {"Click on the Tauri and Sycamore logos to learn more."}}
}

Sycamore / Leptos / Dioxus

这三者有诸多相似性:

  • 都具备响应式WEB功能
  • 都提供类似于JSX的前端UI描述语言
  • 都支持UI组件化
  • 都是用Rust编程语言开发的开源项目
  • 都支持编译为WebAssembly(WASM)在浏览器内运行
  • 都可以作为Rust前端被集成进Tauri 2.0 App

当然它们也都可以脱离Tauri各自独立开发WEB应用(其中Dioxus还可以开发桌面应用和移动应用)。

三者之中目前只有Dioxus支持热加载(Hot Module Reloading, HMR),修改UI代码后无需重新编译即时预览结果,开发体验更佳。

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

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

相关文章

vs封装dll 给C#使用

一&#xff0c;vs创建控制台应用 创建控制台应用得好处时&#xff0c;我们可以自己测试接口&#xff0c;如果接口没有问题&#xff0c;改成dll重新编译一遍就可以。 二&#xff0c; 创建一个c 类&#xff0c;将所需提供得功能 封装到类中。 这样可以将 所有功能&#xff0c;进…

紧跟潮流,将 DeepSeek 集成到 VSCode

Visual Studio Code&#xff08;简称 VSCode&#xff09;是一款由微软开发的免费开源代码编辑器&#xff0c;自 2015 年发布以来&#xff0c;凭借其轻便、强大、且拥有丰富扩展生态的特点&#xff0c;迅速成为了全球开发者的首选工具。VSCode 支持多平台操作系统&#xff0c;包…

html 列动态布局

样式说明&#xff1a; /* 列动态布局&#xff0c;列之间以空格填充 */ li {display: flex;/* flex-direction: column; */justify-content: space-between; }

从O(k*n)到O(1):如何用哈希表终结多层if判断的性能困局

【前言】   本文将以哈希表重构实战为核心&#xff0c;完整展示如何将传统条件匹配逻辑(上千层if-else判断)转化为O(1)的哈希表高效实现。通过指纹验证场景的代码级解剖&#xff0c;您将深入理解&#xff1a;   1.哈希函数设计如何规避冲突陷阱   2.链式寻址法的工程实现…

第 26 场 蓝桥入门赛

3.电子舞龙【算法赛】 - 蓝桥云课 问题描述 话说这年头&#xff0c;连舞龙都得电子化&#xff01;这不&#xff0c;蓝桥村的老程序员王大爷突发奇想&#xff0c;用LED灯带和一堆传感器鼓捣出了一条“电子舞龙”&#xff0c;它能根据程序指令在村里的广场上“翩翩起舞”。 广…

0012—数组

存取一组数据&#xff0c;使用数组。 数组是一组相同类型元素的集合。 要存储1-10的数字&#xff0c;怎么存储&#xff1f; C语言中给了数组的定义&#xff1a;一组相同类型元素的集合。 创建一个空间创建一组数&#xff1a; 一、数组的定义 int arr[10] {1,2,3,4,5,6,7,8,…

详细教程 | 如何使用DolphinScheduler调度Flink实时任务

Apache DolphinScheduler 非常适用于实时数据处理场景&#xff0c;尤其是与 Apache Flink 的集成。DolphinScheduler 提供了丰富的功能&#xff0c;包括任务依赖管理、动态调度、实时监控和日志管理&#xff0c;能够有效简化 Flink 实时任务的管理和部署。通过 DolphinSchedule…

了解传输层TCP协议

目录 一、TCP协议段格式 二、TCP原理 1.确认应答 2.超时重传 3.连接管理 建立连接 断开连接 4.滑动窗口 5.流量控制 6.拥塞控制 7.延时应答 8.捎带应答 9.面向字节流 10.TCP异常情况 TCP&#xff0c;即Transmission Control Protocol&#xff0c;传输控制协议。人如…

idea 如何使用deepseek 保姆级教程

1.安装idea插件codegpt 2.注册deepseek并生成apikey deepseek 开发平台&#xff1a; DeepSeek​​​​​​​ 3.在idea进行codegpt配置 打开idea的File->Settings->Tools->CodeGPT->Providers->Custom OpenAI Chat Completions的URL填写 https://api.deepseek…

Linux之kernel(1)系统基础理论(1)

Linux之Kernel(1)系统基础理论(1) Author: Once Day Date: 2025年2月6日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux内核知识_Once-Day的…

【读书笔记·VLSI电路设计方法解密】问题46:什么是bug覆盖率

在IC设计项目的验证过程中&#xff0c;功能测试&#xff08;通过使用测试平台&#xff09;有助于定位设计错误或漏洞。这个验证过程有三个阶段&#xff1a;构建和启动测试平台、验证基本测试用例以及验证边界情况。 在前两个阶段&#xff0c;漏洞很容易被检测到&#xff0c;因…

【python】简单的flask做页面。一组字母组成的所有单词。这里的输入是一组字母,而输出是所有可能得字母组成的单词列表

目录结构如下&#xff1a; . ├── static │ ├── css │ │ └── styles.css │ └── js │ └── scripts.js ├── templates │ ├── base.html │ ├── case_converter.html │ ├── index.html │ └── word_finder.html ├── app.py ├── tree.py…

【ArcGIS Pro 简介1】

ArcGIS Pro 是由 Esri &#xff08;Environmental Systems Research Institute&#xff09;公司开发的下一代桌面地理信息系统&#xff08;GIS&#xff09;软件&#xff0c;是传统 ArcMap 的现代化替代产品。它结合了强大的空间分析能力、直观的用户界面和先进的三维可视化技术…

JAVA安全—FastJson反序列化利用链跟踪autoType绕过

前言 FastJson这个漏洞我们之前讲过了,今天主要是对它的链条进行分析一下,明白链条的构造原理。 Java安全—log4j日志&FastJson序列化&JNDI注入_log4j漏洞-CSDN博客 漏洞版本 1.2.24及以下没有对序列化的类做校验,导致漏洞产生 1.2.25-1.2.41增加了黑名单限制,…

力扣240 搜索二维矩阵 ll

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,…

C语言按位取反【~】详解,含原码反码补码的0基础讲解【原码反码补码严格意义上来说属于计算机组成原理的范畴,不过这也是学好编程初级阶段的必修课】

目录 概述【适合0基础看的简要描述】&#xff1a; 上述加粗下划线的内容提取版&#xff1a; 从上述概述中提取的核心知识点&#xff0c;需背诵&#xff1a; 整数【包含整数&#xff0c;负整数和0】的原码反码补码相互转换的过程图示&#xff1a; 过程详细刨析&#xff1a;…

StarSpider 星蛛 爬虫 Java框架 可以实现 lazy爬取 实现 HTML 文件的编译,子标签缓存等操作

StarSpider 星蛛 爬虫 Java框架 开源技术栏 StarSpider 能够实现 针对 HTML XSS SQL 数学表达式等杂乱数据的 爬取 解析 提取 需求&#xff01; 目录 文章目录 StarSpider 星蛛 爬虫 Java框架目录介绍如何获取&#xff1f;maven配置 架构是什么样的&#xff1f;结果对象的类…

音频进阶学习十一——离散傅里叶级数DFS

文章目录 前言一、傅里叶级数1.定义2.周期信号序列3.表达式DFSIDFS参数含义 4.DFS公式解析1&#xff09;右边解析 T T T、 f f f、 ω \omega ω的关系求和公式N的释义求和公式K的释义 e j ( − 2 π k n N ) e^{j(\frac{-2\pi kn}{N})} ej(N−2πkn​)的释义 ∑ n 0 N − 1 e…

C++ Primer 成员访问运算符

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

基础入门-算法解密散列对称非对称字典碰撞前后端逆向MD5AESDESRSA

知识点&#xff1a; 0、算法类型-单向散列&对称性&非对称性 1、算法识别加解密-MD5&AES&DES&RSA 2、解密条件寻找-逻辑特征&源码中&JS分析 应用场景&#xff1a; 1、发送数据的时候自动将数据加密发送&#xff08;只需加密即可&#xff09; 安全…