Rust 跨平台-Android 和鸿蒙 OS

news2024/12/27 13:47:16

1. 安装 rustup

rustup 是 Rust 的安装和版本管理工具

$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

该命令会安装 rusup 和最新的稳定版本的 Rust;包括:

  • rustc Rust 编译器,用于将 Rust 代码编译成可执行文件或库。

  • cargo Rust 的包管理器和构建工具,用于管理项目依赖、编译项目、运行测试等。

  • rustfmt 代码格式化工具,用于自动格式化 Rust 代码以符合官方风格指南。

  • clippy 静态分析工具,用于捕捉常见错误和改进代码质量。

  • 其他工具,如rustdoc用于生成文档等。

成功后控制台会输出:Rust is installed now. Great!

macOS 系统上需要安装:xcode-select --install

cargo 在开发中较为常用,算是打交道最多的工具之一

2. 标准库 Rust Standard Library

标准库是 Rust 编程语言的官方库,提供了一系列预先编写好的类型和函数,用来处理常见的任务,如:

  1. 基本数据类型(比如i32u64f32等)。

  2. 集合类型(如Vec<T>HashMap<K, V>等)。

  3. 输入/输出(I/O)操作,包括文件操作和网络编程。

  4. 线程和并发编程工具。

  5. 其他有用的工具,如字符串处理、日期和时间操作等。

渠道

通常情况下安装 rustup 的时候,标准库就已经安装到本地;但是 rust 有几种发布渠道,用于提供不同稳定程度的 Rust 版本,Rust 的三个主要发布渠道是:

  1. Stable(稳定版):这是大多数用户推荐使用的版本。它每六周发布一次,提供最新的功能和改进,但只包括那些经过充分测试和认为稳定的特性。

  2. Beta(测试版):这个版本比 Stable 新,但可能包含一些即将纳入下一个 Stable 版本的特性和改进。它主要用于测试即将发布的功能,以确保它们在正式成为稳定版之前没有问题。

  3. Nightly(每夜构建版):这是最前沿的版本,包括了所有最新开发的特性。这些特性可能未完全稳定或待评估,因此这个版本主要用于实验和评估最新的语言改进。Nightly 版本,顾名思义,每夜更新一次,包括最新的代码提交。

安装

  • 列出已安装的版本

rustup toolchain list
  • 安装新的版本

rustup toolchain install beta

或者

rustup toolchain install nightly

切换版本

切换全局(即默认)Rust 版本,使用rustup default命令:

rustup default stable
rustup default beta
rustup default nightly

这些命令会将你的系统默认 Rust 版本切换为相应的版本。

特定项目切换版本

如果你只想为特定的项目切换 Rust 版本,而不影响全局设置,可以在项目目录内使用以下命令设置目录级别的默认版本:

rustup override set stable
rustup override set beta
rustup override set nightly

补装标准库源码

rustup component add rust-src

每一个 toolchain 都有自己的源码

建议安装 stable 和 nightly 的源码,因为只有 nightly 版本支持编译鸿蒙系统

如果不安装后续鸿蒙 OS 下编译会报错,根据提示安装也行

为特定目标平台编译代码

在 stable 下,rust 支持 android 平台的编译,通过 rustup target list |grep android 可以查看支持的所有平台架构

% rustup target list |grep android                                                                                                                                                    24-03-19 - 15:46:34
aarch64-linux-android (installed)
arm-linux-androideabi (installed)
armv7-linux-androideabi (installed)
i686-linux-android (installed)
thumbv7neon-linux-androideabi (installed)
x86_64-linux-android (installed)

如果已安装,后面会有 (installed) 标识;建议一次性都安装上:

rustup target add aarch64-linux-android arm-linux-androideabi armv7-linux-androideabi i686-linux-android thumbv7neon-linux-androideabi x86_64-linux-android

鸿蒙 OS 下需要切换到 nightly,通过 rustup target list |grep ohos 可以查看支持的所有平台架构:

% rustup target list |grep ohos                                                         
aarch64-unknown-linux-ohos (installed)
armv7-unknown-linux-ohos (installed)
x86_64-unknown-linux-ohos (installed)

同样,建议一次性都安装上

3. 创建 Rust Library 工程

使用命令行创建:

cargo new demo --lib

或者使用 IDE,推荐使用 Jetbrains 的 RustRover

此时目录结构如下:

demo
├── Cargo.toml
└── src
    └── lib.rs

Cargo.toml 的配置

[lib]

[lib]
crate-type = ["cdylib"]

crate-type属性用于指定编译目标类型。这些类型决定了编译器会如何编译你的代码。以下是一些常见的crate-type值及其区别:

1. bin

  • 描述:一个可执行的二进制文件。

  • 使用场景:当你想要创建一个可以直接运行的程序时,使用此类型。大多数应用程序都是以bin类型编译的。

2. lib

  • 描述:一个库文件,可以被其他 Rust 包作为依赖使用。

  • 使用场景:如果你正在开发一个提供函数、类型或特性给其他包使用的库,应选择此类型。

3. rlib

  • 描述:Rust 编译的库文件,包含元数据和符号,供后续的 Rust 编译阶段使用。

  • 使用场景:当你想要编译一个 Rust 库供其他 Rust 项目使用,并期望进行链接和代码生成优化时。

4. dylib

  • 描述:一个动态链接库(DLL),可以在运行时被 Rust 或其他语言的应用程序动态链接。

  • 使用场景:当你想要创建一个可以被多个程序共享的库,或者当你需要和其他使用动态链接的语言互操作时。

5. cdylib

  • 描述:一个为 C 语言接口定制的动态链接库。它移除了 Rust 特有的元数据,只保留了可以从 C 或其他语言调用的符号。

  • 使用场景:当你开发一个 Rust 库,希望能够被 C 或其他语言作为动态链接库使用时。这是创建跨语言共享库的常见方式。

6. staticlib

  • 描述:静态库(.a文件),可以被 C 语言或其他语言的应用程序在编译时静态链接。

  • 使用场景:如果你想要创建一个可以被其他语言静态链接的库,或希望你的 Rust 代码被编译进一个单独的二进制文件,而不依赖于 Rust 的运行时或其他动态库。

7. proc-macro

  • 描述:一个过程宏库,用于创建自定义#[derive]宏或其他类型的宏。

  • 使用场景:当你想要创建新的宏来扩展 Rust 语法,比如自定义派生属性或宏指令时。

[dependencies] 和 [features]

由于需要区分 android 和 ohos 两个平台的特定库,所以有一些依赖库需要配置为可选的,然后使用 cargo 构建的时候添加 --features 参数来分别进行交叉编译

对于 android 平台,需要引入 jni 库,来和 java/kotlin 互相调用

rustnode 互相调用可以使用 node-bindgen,但遗憾的是,node-bindgen并不兼容鸿蒙系统;不过已经有人基于node-bindgen兼容了 ohos:https://crates.io/crates/ohos-node-bindgen

对于 ohos 平台,需要引入 ohos-node-bindgen 库,来和 node 通信;由于 ohos-node-bindgen 依赖 socket2,然而 socket2ohos 下有 bug,所以这里需要使用https://github.com/stuartZhang/socket2.git 来替换 ohos-node-bindgen 内部依赖的socket2版本

最终配置如下

[features]
default = ["android"]
android = ["dep:jni", "dep:android_logger"]
ohos = ["dep:ohos-node-bindgen", "dep:socket2"]

[dependencies]
jni = { version = "0.19.0", optional = true }
android_logger = { version = "0.13.3", optional = true }
ohos-node-bindgen = { version = "6.0.3", optional = true }
socket2 = { version = "0.4.10", optional = true }
dashmap = "5.5.3"
threadpool = "1.8.1"
log = "0.4.21"

[patch.crates-io]
socket2 = { version = "0.4.10", git = "https://github.com/stuartZhang/socket2.git", branch = "v0.4.x" }

也就是说:

  • dashmapthreadpoollog 是所有平台下都参与编译的库

  • android 单独编译:jniandroid_logger

  • ohos 单独编译:ohos-node-bindgensocket2

  • 另外,features 的默认值为 android

编写代码 - lib.rs

由于存在不同的 features,所以对于 android:

#[cfg(feature = "android")]
#[no_mangle]
pub extern "system" fn Java_com_haier_uhome_uplus_hook_monitor_app_NativeLib_hello(
    env: JNIEnv,
    _class: JClass,
) -> jstring {
    // 将 Rust 字符串转换为 JNI 字符串
    let result = env.new_string("Hello from Rust!").expect("Couldn't create Java string!");
    // 返回结果
    result.into_inner()
}

#[cfg(feature = "android")]:与上述 features 对应

#[no_mangle] 则是禁用驼峰警告

对于 ohos:

#[cfg(feature = "ohos")]
#[ohos_node_bindgen]
pub extern "C" fn add(l: i32, r: i32) -> i32 {
    l + r
}

#[cfg(feature = "ohos")]:与上述 features 对应

#[ohos_node_bindgen] 则是标识 add 函数可以被 node 端调用

node-bindgen 的大致原理如下:

1. FFI(外部函数接口)

Node.js 的原生模块基于 C++ 和 Node.js 的 N-API(原生 API),N-API 提供了一套与 V8 引擎解耦的接口,使原生模块在 Node.js 版本升级时保持兼容。node-bindgen 底层利用 Rust 的外部函数接口(FFI)能力,通过这些接口与 Node.js 通信。

Rust 的 FFI 功能允许其调用 C 语言 API。因此,node-bindgen 实际上是通过 Rust 的 FFI 调用 Node.js 的 N-API 来创建和管理 JavaScript 值,以及执行与 JavaScript 环境的交互。

2. 宏和属性

node-bindgen 提供了一系列宏(例如 #[node_bindgen]),这些宏在编译时自动生成将 Rust 函数暴露为 Node.js 可调用函数的胶水代码。这个过程包括自动生成用于参数转换和返回值处理的代码,使 Rust 函数能够直接接收来自 JavaScript 的参数并返回可以直接在 JavaScript 中使用的结果。

3. 内存管理

Rust 和 JavaScript 之间的内存管理是 node-bindgen 的关键部分。Rust 有自己的内存管理规则,主要基于所有权和生命周期,而 JavaScript 的内存则由垃圾收集器自动管理。node-bindgen 必须确保在这两种内存管理模型之间正确地桥接,包括处理 Rust 中的数据所有权转移和确保 JavaScript 对象在需要时保持存活。

4. 异步操作

Node.js 广泛使用异步操作,而 Rust 也有强大的异步支持。node-bindgen 支持将 Rust 的异步操作暴露给 Node.js。这通过将 Rust 的 Future 转换为 Node.js 的 Promise 来实现。node-bindgen 会自动处理这种转换,允许开发者以 Promise 的形式在 JavaScript 中接收 Rust 异步操作的结果。

5. 类型转换

node-bindgen 自动处理 Rust 类型和 JavaScript 类型之间的转换。对于简单类型(如数字和字符串),这通常是直接的。但对于复杂类型(如结构体或枚举),node-bindgen 生成的代码会负责序列化和反序列化操作,确保两种语言之间可以无缝交换复杂数据结构。

总结

node-bindgen 利用 Rust 的 FFI 能力、宏系统、强类型系统和异步特性,提供了一种高效、类型安全的方式来将 Rust 代码与 Node.js 集成。它自动处理大部分繁琐的胶水代码编写工作,使得 Rust 和 Node.js 之间的交互变得更加简单直接。这样的设计允许开发者专注于实现应用逻辑,而无需深入底层的语言绑定细节。理解颇为浅陋,如有任何问题可私 1239604859@qq.com 讨论

编译 - android 平台的产物

官方提供了 cargo-ndk 工具,它简化了为 Android 使用 Rust 编写原生代码库(.so 文件)的过程。

  • 下载安卓 NDK,并配置到环境变量

export ANDROID_HOME=$HOME/ssd/Android/sdk
PATH="$ANDROID_HOME/ndk-bundle:$PATH"
export PATH
  • 安装 cargo-ndk

cargo install cargo-ndk
  • 使用 cargo-ndk 构建你的项目

cargo ndk -t armv7-linux-androideabi -t aarch64-linux-android -o ../../MonitorTestClient/app/src/main/jniLibs build --release
  • 参数解释

    • -t--target:指定目标架构

    • -o--output:指定输出目录,这里的目录会用于存放编译生成的 .so 文件

    • build:是 cargo 的子命令,用于编译项目,会传递它以及任何附加参数给 cargo build

编译 - ohos 平台的产物

官方没有为鸿蒙系统提供类似cargo-ndk的工具,需要手动配置编译参数

1. 首先切换到 nightly 渠道

rustup override set nightly

2. 配置环境变量:

# huawei
export OHOS_HOME=$HOME/ssd/huawei/sdk
export OHOS_API_V=9
export OHOS_CORE_V=3.1.0
export OH_NDK_ROOT=$OHOS_HOME/openharmony/$OHOS_API_V/native
PATH="$OHOS_HOME/hmscore/$OHOS_CORE_V/toolchains:$PATH"
export PATH

单独配置 OHOS_API_V 的好处是如果华为更新了 Native SDK,可以更方便的动态切换

3. 创建 ohos 对应 target 的可执行脚本,例如和config.toml中 [target.aarch64-unknown-linux-ohos] 对应

  • 创建位置:~/.cargo

  • aarch64-unknown-linux-ohos-clang.sh

#!/bin/sh
. $HOME/.bash_profile
exec $OH_NDK_ROOT/llvm/bin/clang \
  -target aarch64-linux-ohos \
  --sysroot=$OH_NDK_ROOT/sysroot \
  -D__MUSL__ \
  "$@"
  • armv7-unknown-linux-ohos-clang.sh

#!/bin/sh
. $HOME/.bash_profile
exec $OH_NDK_ROOT/llvm/bin/clang \
  -target arm-linux-ohos \
  --sysroot=$OH_NDK_ROOT/sysroot \
  -D__MUSL__ \
  -march=armv7-a \
  -mfloat-abi=softfp \
  -mtune=generic-armv7-a \
  -mthumb \
  "$@"
  • x86_64-unknown-linux-ohos-clang.sh

#!/bin/sh
. $HOME/.bash_profile
exec $OH_NDK_ROOT/llvm/bin/clang \
  -target x86_64-linux-ohos \
  --sysroot=$OH_NDK_ROOT/sysroot \
  -D__MUSL__ \
  "$@"
  • . $HOME/.bash_profile 根据实际情况进行修改,只要能拿到 $OH_NDK_ROOT 即可

4. 通用配置:config.toml

  • 创建位置:~/.cargo

# 鸿蒙编译工具链-目前只能手动配置:
[target.aarch64-unknown-linux-ohos]
linker = ".cargo/aarch64-unknown-linux-ohos-clang.sh"

[target.armv7-unknown-linux-ohos]
linker = ".cargo/armv7-unknown-linux-ohos-clang.sh"

[target.x86_64-unknown-linux-ohos]
linker = ".cargo/x86_64-unknown-linux-ohos-clang.sh"

# 会概率性地失败于exit code: 0xc0000005, STATUS_ACCESS_VIOLATION错误 - https://rustcc.cn/article?id=568d35d6-b782-49e9-b9b1-5d870d28f927
[profile.dev.package.compiler_builtins]
opt-level = 2

[alias]
ohos-build = ["build", "-Zbuild-std", "--target=aarch64-unknown-linux-ohos", "--target=armv7-unknown-linux-ohos", "--target=x86_64-unknown-linux-ohos"]
  • [alias] 作用是使得:

        cargo ohos-build --release 等价于 cargo build -Zbuild-std --target=aarch64-unknown-linux-ohos --target=armv7-unknown-linux-ohos --target=x86_64-unknown-linux-ohos --release

  • 对于我们演示的 demo 工程,最终编译命令行如下

cargo ohos-build --release --features ohos

4. Android 工程测试 rust 产物

把动态库拷贝到 app 模块中

src
├── androidTest
├── main
│   ├── jniLibs
│   │   ├── arm64-v8a
│   │   │   └── libdemo.so
│   │   └── armeabi-v7a
│   │       └── libdemo.so

创建对应包名的单例

object NativeLib {
  init {
    System.loadLibrary("demo")
  }

  external fun hello(): String

  external fun mapTest()
}

在 MainActivity 中调用

val nstr = NativeLib.hello()
Log.d(TAG, "onCreate: $nstr")
2024-03-19 19:32:32.207 11941-11941 MainActivity             D  onCreate: Hello from Rust!

5. ohos 工程测试 rust 产物

把动态库拷贝到 entry 模块中

entry
├── build-profile.json5
├── hvigorfile.ts
├── libs
│   ├── arm64-v8a
│   │   └── libdemo.so
│   └── armeabi-v7a
│       └── libdemo.so

在 Index.ets 中

import hello from "libdemo.so"

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Button("计 算")
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            let sum = hello.add(3, 5);
            this.message = "3 + 5 = " + sum.toString();
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

运行结果:

      

6. 团队介绍

三翼鸟数字化技术平台-智家APP平台」通过持续迭代演进移动端一站式接入平台为三翼鸟APP、智家APP等多个APP提供基础运行框架、系统通用能力API、日志、网络访问、页面路由、动态化框架、UI组件库等移动端开发通用基础设施;通过Z·ONE平台为三翼鸟子领域提供项目管理和技术实践支撑能力,完成从代码托管、CI/CD系统、业务发布、线上实时监控等Devops与工程效能基础设施搭建。

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

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

相关文章

深入理解SSH:网络安全的守护者

在当今数字化时代&#xff0c;网络安全已成为全球关注的焦点。随着网络攻击手段的不断升级&#xff0c;保护数据传输的安全性变得尤为重要。SSH&#xff08;Secure Shell&#xff09;作为一种安全的网络协议&#xff0c;为远程登录和网络服务提供了强大的安全保障&#xff0c;成…

【数据结构】(C语言):动态数组

动态数组&#xff1a; 内存区域连续&#xff0c;即每个元素的内存地址连续。可用索引查看元素&#xff0c;数组[索引号]。指定位置删除元素&#xff0c;该位置之后的元素全部往前移动一位。指定位置添加元素&#xff0c;从最后到该位置的元素全部往后移动一位。物理大小&#…

自费5K,测评安德迈、小米、希喂三款宠物空气净化器谁才是高性价比之王

最近&#xff0c;家里的猫咪掉毛严重&#xff0c;简直成了一个活生生的蒲公英&#xff0c;家中、空气中各处都弥漫着猫浮毛甚至所有衣物都覆盖着一层厚厚的猫毛。令人难以置信的是&#xff0c;有时我甚至在抠出的眼屎中都能发现夹杂着几根猫毛。真的超级困扰了。但其实最空气中…

句法分析概述

第1关&#xff1a;句法分析概述 任务描述 本关任务&#xff1a;通过对句法分析基本概念的学习&#xff0c;完成相应的选择题。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 句法分析的基础概念&#xff1b; 句法分析的数据集和评测方法。 句法分析简介…

ubuntu如何切换到root用户

1、主要指令&#xff1a; sudo -i su root 2、示例 3、其他说明 在Ubuntu&#xff08;以及大多数其他基于Linux的操作系统中&#xff09;&#xff0c;切换到root用户通常意味着获得了对系统的完全访问权限。这种权限允许执行以下操作&#xff08;但不限于这些&#xff09;…

深度学习论文: Surge Phenomenon in Optimal Learning Rate and Batch Size Scaling

深度学习论文: Surge Phenomenon in Optimal Learning Rate and Batch Size Scaling Surge Phenomenon in Optimal Learning Rate and Batch Size Scaling PDF:https://arxiv.org/pdf/2405.14578 PyTorch: https://github.com/shanglianlm0525/PyTorch-Networks 1 概述 本文研…

使用Python进行Socket接口测试

大家好&#xff0c;在现代软件开发中&#xff0c;网络通信是不可或缺的一部分。无论是传输数据、获取信息还是实现实时通讯&#xff0c;都离不开可靠的网络连接和有效的数据交换机制。而在网络编程的基础中&#xff0c;Socket&#xff08;套接字&#xff09;技术扮演了重要角色…

2024 6.17~6.23 周报

一、上周工作 吴恩达的机器学习、实验-回顾之前密集连接部分 二、本周计划 继续机器学习&#xff0c;同时思考实验如何修改&#xff0c;开始整理代码 三、完成情况 3.1 多类特征、多元线性回归的梯度下降、特征缩放、逻辑回归 多类特征&#xff1a; 多元线性回归的梯度下…

基于PHP的长城景区信息管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的长城景区信息管理系统 一 介绍 此长城景区信息管理系统基于原生PHP开发&#xff0c;数据库mysql。系统角色分为用户和管理员。 技术栈&#xff1a;phpmysqlphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 浏览长城景区信息(旅…

出手便是王炸,曙光存储将高端存储推向新高度

二十年磨一剑&#xff0c;今朝试锋芒。 近日&#xff0c;曙光存储重磅发布全球首个亿级IOPS集中式全闪存储FlashNexus&#xff0c;正式宣告进入高端存储市场。 作为存储产业皇冠上的明珠&#xff0c;高端存储一向以技术难度大、市场准入门槛高和竞争格局稳定著称&#xff0c;…

React的Props、生命周期

Props 的只读性 “Props” 是 React 中用于传递数据给组件的一种机制&#xff0c;通常作为组件的参数进行传递。在 React 中&#xff0c;props 是只读的&#xff0c;意味着一旦将数据传递给组件的 props&#xff0c;组件就不能直接修改这些 props 的值。所以组件无论是使用函数…

Studying-代码随想录训练营day22| 回溯理论基础、77.组合、216.组合总和II、17.电话号码的字母组合

第22天&#xff0c;回溯章节开始&#xff01;一大算法难点&#xff0c;加油加油&#xff01; 回溯理论基础组合问题的剪枝操作 文档讲解&#xff1a;代码随想录回溯理论基础 视频讲解&#xff1a;回溯理论基础 回溯法也叫回溯搜索法&#xff0c;它是一种搜索&#xff0c;遍历的…

数值稳定性、模型初始化和激活函数

一、数值稳定性&#xff1a;神经网络很深的时候数据非常容易不稳定 1、神经网络梯度 h^(t-1)是t-1层的输出&#xff0c;也就是t层的输入&#xff0c;y是需要优化的目标函数&#xff0c;向量关于向量的倒数是一个矩阵。 2、问题&#xff1a;梯度爆炸、梯度消失 &#xff08;1&…

leetcode-19-回溯

引自代码随想录 [77]组合 给定两个整数 n 和 k&#xff0c;返回 1 ... n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4]] 1、大致逻辑 k为树的深度&#xff0c;到叶子节点的路径即为一个结果 开始索引保证不重复…

当了面试官才知道:做好这3点,面试成功率至少提高50%

关于辉哥&#xff1a; 资深IT从业者&#xff0c; 曾就职于阿里、腾讯、美团、中信科等互联网公司和央企&#xff1b; 两岁小男孩的父亲。 不定期分享职场 | 婚姻 | 育儿 | 个人成长心得体会 关注我&#xff0c;一起学习和成长。 最近作为公司社招面…

一文入门CMake

我们前几篇文章已经入门了gcc和Makefile&#xff0c;现在可以来玩玩CMake了。 CMake和Makefile是差不多的&#xff0c;基本上是可以相互替换使用的。CMAke可以生成Makefile&#xff0c;所以本质上我们还是用的Makefile&#xff0c;只不过用了CMake就不用再写Makefile了&#x…

Zookeeper 四、Zookeeper应用场景

Zookeeper是一个典型的发布/订阅模式的分布式数据管理与协调框架&#xff0c;我们可以使用它来进行分布式数据的发布与订阅。另一方面&#xff0c;通过对Zookeeper中丰富的数据节点类型进行交叉使用&#xff0c;配合Watcher事件通知机制&#xff0c;可以非常方便地构建一系列分…

Day.js

Day.js 是什么&#xff1f; Day.js是一个极简的JavaScript库&#xff0c;可以为现代浏览器解析、验证、操作和显示日期和时间。 Day.js中文网 为什么要使用Day.js &#xff1f; 因为Day.js文件只有2KB左右&#xff0c;下载、解析和执行的JavaScript更少&#xff0c;为代码留下更…

如何避免爬取网站时IP被封?

互联网协议 (IP) 地址是识别网络抓取工具的最常见方式。IP 是每个互联网交换的核心&#xff0c;对其进行跟踪和分析可以了解很多有关连接客户端的信息。 在网络抓取中&#xff0c;IP 跟踪和分析&#xff08;又名指纹&#xff09;通常用于限制和阻止网络抓取程序或其他不需要的访…

配电房挂轨巡检机器人

配电房作为电网中的重要组成部分。其运行的的安全和稳定性直接影响到电力供应的质量。然而&#xff0c;传统的人工巡检模式存在诸多弊端&#xff0c;例如巡检效率低下、人员安全难以保障、巡检结果主观性强等问题。为了解决这些问题&#xff0c;旗晟机器人推出B3系列升降云台轨…