Rust 与 dora-rs:吃透核心概念,手把手打造跨语言的机器人实时数据流应用

Rust 与 dora-rs:吃透核心概念,手把手打造跨语言的机器人实时数据流应用

只需一张YAML蓝图,就能将Python的灵活、Rust的性能与实时数据流无缝缝合。

机器人与边缘 AI 开发中,我们长期面临一个两难选择:是拥抱 Python 的生态便利,还是追求 C++/Rust 的极致性能?在传统架构,由于跨进程通信(IPC)的序列化开销往往是实时系统的噩梦,导致二者难以兼得。

dora-rs 的出现打破了这一僵局。作为一个基于 Rust 的数据流框架,它利用 Apache Arrow 实现 零拷贝(Zero-copy) 消息传递,完美缝合了 Rust 的安全性与 Python 的灵活性。

本文将深入解构 dora-rs 的核心架构,并带你手写一个 "Rust 采集 + Rust 运算 + Python 实时可视化" 的完整混合语言数据流系统。

一 核心概念:理解 dora-rs 的世界观

1. 数据流蓝图:应用的"施工图"

dora-rs 中,应用被抽象为一个由节点和连接构成的有向数据流图Dataflow 是应用的"施工图",采用声明式的 YAML 格式定义,它不关心具体代码怎么写,只关心数据从哪里来(Source)、经过谁处理(Process)、流向哪里(Sink)。这种设计让系统架构一目了然,彻底告别了复杂的 Launch 文件地狱。

yaml 复制代码
# 核心结构:nodes 列表定义了所有处理单元
nodes:
  # 一个传感器节点
  - id: temperature_sensor
    path: sensor.py
    outputs:
      - raw_temperature
  # 一个处理节点
  - id: data_processor
    path: ./target/release/processor_node
    inputs:
      temp: temperature_sensor/raw_temperature # 连接至此!
    outputs:
      - smoothed_temperature

inputs使用来源节点/输出名的格式建立连接,这种声明式配置让复杂系统的结构和数据流向一目了然

当你运行 dora start dataflow.yml 时,dora-rs 的运行时便会读取此蓝图,自动启动所有节点进程,并按照图示建立高效的数据通道。

2. 节点与操作符:工人与工具箱

这是 dora-rs 性能优化的关键,很多开发者容易混淆:

  • Node (节点) :是一个独立的系统进程,拥有完全隔离的内存空间。它像一名专职工人,可以封装完整的、独立的服务,或运行有特定环境依赖(如Python AI模型库)的代码。节点间通信需要进程间通信机制。

  • Operator (操作符) :是运行在同一节点进程内 的模块化逻辑单元,用于在一种名为 Dora Runtime Node 的特殊节点内运行。Operator 允许你将单个节点的工作分解为可复用的独立步骤。它像工人手边的专用工具。

特性 Node (节点) Operator (操作符)
运行方式 独立进程(Process) 单进程
通信成本 跨进程(共享内存优化) 函数调用级延迟
适用场景 跨语言混编、故障隔离 高频计算、模块化复用
API dora Node API dora Operator API

最佳实践:将性能瓶颈模块(如图像预处理、控制算法)实现为Rust操作符;将依赖复杂生态(如PyTorch)的部分实现为Python节点。

3. 事件流:永不阻塞的"收件箱"

节点或操作符如何感知新数据?答案是通过事件流 。这是一个异步通道,dora-rs 运行时会向节点推送各类事件,你的代码只需循环监听并处理。

五大核心事件类型:

Rust事件循环方式

rust 复制代码
// Rust事件处理模式
let (mut node, mut events) = DoraNode::init_from_env()?;
while let Some(event) = events.recv() {
    match event {
        Event::Input { id, data, metadata } => {
            println!("收到输入: {}, 数据大小: {}", id, data.len());
            // ...业务逻辑...
        }
        Event::InputClosed { id } => {
            println!("上游节点关闭: {}", id);  // 处理断连
        }
        Event::Stop => {
            println!("收到停止信号"); break;    // 优雅退出
        }
        Event::Reload => { /* 热更新配置 */ }
        Event::Error { id, error } => {
            println!("节点 {} 发生错误: {:?}", id, error);
        }
    }
}

Python事件循环方式

python 复制代码
from dora import Node

# Initialize the node - connects to the dora runtime
node = Node() 
print("Node initialized. Waiting for events...")

# Enter the loop to process events from the stream
for event in node: 
    event_type = event["type"]
    event_id = event["id"] # Often the input ID

    print(f"Received event: Type={event_type}, ID={event_id}")

    if event_type == "INPUT":
        # Handle incoming data
        print(f"  -> Data received on input '{event_id}'")
        data = event["value"] 
        # Now you would process the 'data'...
        
        # Example: If it's from an input named 'camera_image'
        if event_id == "camera_image":
            print("  -> Processing camera image...")
            # process_image(data)
            # node.send_output(...) # Send results if needed
            pass # Placeholder for actual processing

    elif event_type == "STOP":
        # Handle stop command
        print("  -> Received STOP command. Exiting loop.")
        break # Exit the main loop

    elif event_type == "InputClosed":
        # Handle input closing
        print(f"  -> Input '{event_id}' closed.")
        # Decide if the node should continue or stop

    elif event_type == "RELOAD":
        # Handle reload command
        print("  -> Received RELOAD command.")
        # Reload configuration if implemented

    elif event_type == "ERROR":
        # Handle a stream error
        print(f"  -> Received ERROR: {event['error']}")
        # Log or react to the error

print("Node stopping gracefully.")

4. Arrow Data:零拷贝的魔法

处理图像、点云时,复制数据性能致命。dora-rsApache Arrow 实现零拷贝 传输------数据存在共享内存,所有节点直接读取,无需序列化。

Arrow 定义了一种标准化的内存中列式数据格式 。关键在于,当数据大小超过 4KB 阈值时,dora-rs 会将其放入共享内存 。发送方写入,接收方直接读取同一块内存,实现真正的零拷贝

  • 共享内存 :大于 4KB 的数据自动走共享内存。
  • Drop Token:智能的内存管理机制,确保所有接收方读完数据后,内存才会释放。
python 复制代码
# Python节点发送Arrow数据(零拷贝)
import pyarrow as pa
from dora import Node

node = Node()
# 创建一个包含温度值的Arrow数组
temperature_data = pa.array([25.7], type=pa.float32())
# 发送时,dora自动处理共享内存分配
node.send_output("temperature", temperature_data)
rust 复制代码
// Rust节点接收并处理同样的Arrow数据
use arrow::array::Float32Array;

if let Event::Input { data, .. } = event {
    let array: &Float32Array = data.as_any().downcast_ref().unwrap();
    let temp = array.value(0); // 直接访问内存,无拷贝!
    println!("Received temperature: {}", temp);
}

5️. 多语言API绑定:全家桶支持

dora-rs提供Rust、Python、C/C++原生API,风格贴合各自语言习惯。

Rust API(类型安全,性能天花板):

rust 复制代码
let (mut node, mut events) = DoraNode::init_from_env()?;

while let Some(event) = events.recv() {
    if let Event::Input { data, metadata } = event {
        let array: Float32Array = data.as_any().downcast_ref()?;
        node.send_output("result", metadata.parameters, processed_array)?;
    }
}

Python API(简洁高效,AI首选):

python 复制代码
from dora import Node
import pyarrow as pa

node = Node()
for event in node:
    if event["type"] == "INPUT":
        array = event["value"]  # 自动转为pyarrow.Array
        node.send_output("result", pa.array(processed))

C++ API(底层控制):

cpp 复制代码
auto dora_node = init_dora_node();

while (auto event = dora_node.events->next()) {
    auto type = event_type(event);
    if(type == DoraEventType::Input)
    {
        struct ArrowArray c_array;
        struct ArrowSchema c_schema;

        auto result = event_as_arrow_input(
        std::move(event),
        reinterpret_cast(&c_array),
        reinterpret_cast(&c_schema)
        );

        if (!result.error.empty()) {
            std::cerr << "Error getting Arrow array: " << result.error << std::endl;
            return -1;
        }

        auto result2 = arrow::ImportArray(&c_array, &c_schema);
        if (!result2.ok()) {
            std::cerr << "Error importing Arrow array: " << result2.status().ToString() << std::endl;
            return -1;
        }
        std::shared_ptr input_array = result2.ValueOrDie();

        // Now you can use input_array with Arrow's API
    }
}

6️. CLI:指挥中心三剑客

bash 复制代码
# 构建依赖(解析YAML,自动安装/编译)
dora build pipeline.yml

# 启动Daemon和Coordinator (如果之前没启动)
dora up

# 运行数据流
dora start pipeline.yml

# 查看运行状态
dora list

# 停止所有运行中的Dataflow
dora destroy # 一键全停

7️. Daemon & Coordinator:幕后兄弟连

  • Daemon(守护进程):单机的"车间主任",管理本地节点、分配共享内存、监控进程健康、处理本地通信。
  • Coordinator(协调器):分布式的"总经理",协调多机Daemon、管理跨网络路由、维护全局状态。

单机模式CLI → Daemon → Nodes
分布式模式CLI → Coordinator → 多Daemon → Nodes


二 实战:构建混合语言的温度监控系统

通过模拟温度传感器、数据处理、异常检测、日志记录和可视化,展示 dora-rs 的多语言、零拷贝、分布式能力。使用 rust 节点实现温度传感器、数据处理、异常检测,python 节点实现实时可视化。

项目结构

bash 复制代码
dora-temp-monitor-rust/
├── sensor-node/          # Rust 温度传感器模拟节点
│   ├── Cargo.toml
│   └── src/main.rs
├── processor-node/       # Rust 数据处理和异常检测节点
│   ├── Cargo.toml
│   └── src/main.rs
├── logger-node/          # Rust 日志和终端可视化节点
│   ├── Cargo.toml
│   └── src/main.rs
├── visualizer-node/      # Python 可视化节点
│   ├── .venv/
│   └── visualizer_node/
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   └── main.py
│   ├── pyproject.toml
├── dataflow.yml          # Dora 数据流配置文件
└── Cargo.toml            # Rust 项目依赖管理

1. 创建项目

bash 复制代码
# 创建项目, rust语言
dora new --lang rust dora-temp-monitor-rust

# 删除默认创建的节点
rm -rf listener_1 talker_1 talker_2

# 创建自定义节点 - 温度传感器模拟节点
dora new --kind node  --lang rust sensor-node
# Created new Rust custom node `sensor-node` at ./sensor-node

# 创建自定义节点 - 数据处理和异常检测节点
dora new --kind node  --lang rust processor-node
# Created new Rust custom node `processor-node` at ./processor-node
# 创建自定义节点 - 日志记录和终端可视化节点
dora new --kind node  --lang rust logger-node
# Created new Rust custom node `logger-node` at ./logger-node

# 创建自定义节点 - Python可视化节点
dora new --kind node  --lang python visualizer-node
# Created new Python custom node `visualizer-node` at ./visualizer-node

2. 安装uv

uv 是新一代 Python 包管理器

bash 复制代码
cargo install uv

3. 编写温度模拟传感器节点

通过 rand crate 模拟温度数据, 并添加正弦趋势模拟真实环境变化。

sensor-node/Cargo.toml

toml 复制代码
[package]
name = "sensor_node"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dora-node-api = "0.3.13"
anyhow = "1.0"
rand = "0.9.2"

sensor-node/src/main.rs

rust 复制代码
use dora_node_api::{arrow::array::Float32Array, dora_core::config::DataId, DoraNode, Event};
use rand::Rng;
use std::error::Error;
use std::time::Instant;

fn main() -> Result<(), Box<dyn Error>> {
    let (mut node, mut events) = DoraNode::init_from_env()?;
    let output = DataId::from("temp_raw".to_owned());
    println!("🌡️ 传感器节点启动 (M1 Pro优化版)");

    let mut rng = rand::rng();
    let start = Instant::now();

    while let Some(event) = events.recv() {
        // println!("Received event: {:?}", event);
        match event {
            Event::Input {
                id,
                metadata,
                data: _,
            } => match id.as_str() {
                "tick" => {
                    // 模拟带噪声的温度数据
                    let temp = 25.0
                        + rng.random_range(-5.0..5.0)
                        + (start.elapsed().as_secs_f32() * 0.01).sin() * 3.0; // 添加正弦趋势

                    let temp_array = Float32Array::from(vec![temp]);

                    node.send_output(output.clone(), metadata.parameters, temp_array)?;
                }
                other => eprintln!("Received input `{other}`"),
            },
            _ => {}
        }
    }

    Ok(())
}

4. 编写数据处理和异常检测节点

通过滑动窗口计算温度平滑值, 并检测异常值(超过阈值的突变)。

processor-node/Cargo.toml

toml 复制代码
[package]
name = "processor_node"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dora-node-api = "0.3.13"
anyhow = "1.0"

processor-node/src/main.rs

rust 复制代码
use dora_node_api::{
    arrow::array::{Float32Array, StringArray},
    dora_core::config::DataId,
    DoraNode, Event,
};
use std::collections::VecDeque;
use std::error::Error;

struct TemperatureProcessor {
    window: VecDeque<f32>,
    threshold: f32, // 异常阈值
}

impl TemperatureProcessor {
    fn new(window_size: usize, threshold: f32) -> Self {
        Self {
            window: VecDeque::with_capacity(window_size),
            threshold,
        }
    }

    fn process(&mut self, temp: f32) -> (f32, Option<String>) {
        self.window.push_back(temp);
        if self.window.len() > 10 {
            self.window.pop_front();
        }

        let avg = self.window.iter().sum::<f32>() / self.window.len() as f32;

        // 异常检测逻辑
        let alert = if (temp - avg).abs() > self.threshold {
            Some(format!(
                "⚠️ 温度突变: {:.1}°C (偏离均值{:.1}°C)",
                temp,
                (temp - avg).abs()
            ))
        } else {
            None
        };

        (avg, alert)
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let (mut node, mut events) = DoraNode::init_from_env()?;
    let output_temp_smoothed = DataId::from("temp_smoothed".to_owned());
    let output_temp_alert = DataId::from("temp_smootemp_alertthed".to_owned());
    println!("🧮 处理器节点启动 (滑动窗口+异常检测)");

    let mut processor = TemperatureProcessor::new(10, 3.0);

    while let Some(event) = events.recv() {
        match event {
            Event::Input { id, metadata, data } => match id.as_str() {
                "temp" => {
                    let array = data
                        .as_any()
                        .downcast_ref::<Float32Array>()
                        .ok_or("类型转换失败")?;
                    let temp = array.value(0) as f32;

                    let (avg, alert) = processor.process(temp);

                    // 发送平滑数据
                    let avg_array = Float32Array::from(vec![avg]);
                    node.send_output(
                        output_temp_smoothed.clone(),
                        metadata.parameters.clone(),
                        avg_array,
                    )?;

                    // 如果有异常,发送警报
                    if let Some(alert_msg) = alert {
                        let alert_array = StringArray::from(vec![alert_msg]);
                        node.send_output(
                            output_temp_alert.clone(),
                            metadata.parameters,
                            alert_array,
                        )?;
                    }
                }
                _ => {}
            },
            _ => {}
        }
    }

    Ok(())
}

5. 日志节点(终端可视化)

通过滑动窗口计算的平滑温度值, 在终端实时显示柱状图, 异常值则以文本形式提示。

logger-node/Cargo.toml

toml 复制代码
[package]
name = "logger_node"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dora-node-api = "0.3.13"
anyhow = "1.0"

logger-node/src/main.rs

rust 复制代码
use dora_node_api::{
    arrow::array::{Float32Array, StringArray},
    DoraNode, Event,
};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let (mut _node, mut events) = DoraNode::init_from_env()?;
    println!("日志节点启动");
    while let Some(event) = events.recv() {
        match event {
            Event::Input {
                id,
                metadata: _,
                data,
            } => match id.as_str() {
                "smoothed" => {
                    let array = data
                        .as_any()
                        .downcast_ref::<Float32Array>()
                        .ok_or("转换失败")?;
                    let temp = array.value(0);

                    // 终端柱状图(M1终端性能强劲)
                    let bar = "█".repeat((temp * 2.0) as usize);
                    println!("\r[{:4.1}°C] {}", temp, bar);
                }
                "alert" => {
                    let array = data
                        .as_any()
                        .downcast_ref::<StringArray>()
                        .ok_or("转换失败")?;
                    println!("\n🚨 {}", array.value(0));
                }
                other => eprintln!("Logger: Received input `{}`", other),
            },
            _ => {}
        }
    }

    Ok(())
}

6. 编写可视化节点(实时可视化)

通过 pythonmatplotlib 库, 实现实时温度柱状图可视化,通过 uv 来管理 python 依赖。

visualizer-node/pyproject.toml

toml 复制代码
[project]
name = "visualizer-node"
version = "0.0.0"
authors = [{ name = "Your Name", email = "email@email.com" }]
description = "visualizer-node"
license = { text = "MIT" }
readme = "README.md"
requires-python = ">=3.8"

dependencies = [
    "dora-rs >= 0.3.9",
    "matplotlib>=3.7.5",
    "pyarrow>=22.0.0"
]

[dependency-groups]
dev = ["pytest >=8.1.1", "ruff >=0.9.1"]

[tool.uv.sources]
default = [
    { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
]

[project.scripts]
visualizer-node = "visualizer_node.main:main"

[tool.ruff.lint]
extend-select = [
  "D",   # pydocstyle
  "UP"
]

visualizer-node/visualizer_node/main.py

python 复制代码
"""TODO: Add docstring."""

#!/usr/bin/env python3
# visualizer.py - 新增节点:实时温度曲线图
import threading
from collections import deque

import matplotlib.animation as animation
import matplotlib.pyplot as plt
import pyarrow as pa
from dora import Node


class TempVisualizer:
    def __init__(self, max_points=100):
        self.max_points = max_points
        self.timestamps = deque(maxlen=max_points)
        self.temperatures = deque(maxlen=max_points)
        self.fig, self.ax = plt.subplots(figsize=(10, 6))
        (self.line,) = self.ax.plot([], [], "b-", label="Temperature Curve")
        self.ax.set_ylim(15, 40)
        self.ax.set_xlim(0, max_points)
        self.ax.set_xlabel("Timestamp")
        self.ax.set_ylabel("Temperature (°C)")
        self.ax.set_title("Real Time Temperature Monitoring (M1 Pro)")
        self.ax.legend()
        self.ax.grid(True)

    def update_plot(self, frame):
        self.line.set_data(range(len(self.temperatures)), self.temperatures)
        self.ax.set_xlim(0, max(len(self.temperatures), self.max_points))
        return (self.line,)


def data_receiver(visualizer):
    """后台线程接收dora数据"""

    node = Node()
    for event in node:
        if event["type"] == "INPUT" and event["id"] == "data":
            array = event["value"]
            temp = array[0].as_py()
            visualizer.temperatures.append(temp)
            visualizer.timestamps.append(len(visualizer.timestamps))


def main():
    visualizer = TempVisualizer()
    # 启动数据接收线程(非阻塞)
    data_thread = threading.Thread(target=data_receiver, args=(visualizer,))
    data_thread.daemon = True
    data_thread.start()

    # 启动matplotlib动画
    ani = animation.FuncAnimation(
        visualizer.fig,
        visualizer.update_plot,
        interval=100,  # 100ms刷新一次
        blit=True,
    )
    plt.show()


if __name__ == "__main__":
    main()

7. 完整数据流配置

yaml 复制代码
# dataflow.yml (完整四节点版)
nodes:
  - id: temp_sensor
    build: cargo build -p sensor_node
    path: target/debug/sensor_node
    inputs:
      tick: dora/timer/millis/100
    outputs:
      - temp_raw

  - id: data_processor
    build: cargo build -p processor_node
    path: target/debug/processor_node
    inputs:
      temp: temp_sensor/temp_raw
    outputs:
      - temp_smoothed
      - temp_alert

  - id: logger
    build: cargo build -p logger_node
    path: target/debug/logger_node
    inputs:
      smoothed: data_processor/temp_smoothed
      alert: data_processor/temp_alert

  - id: visualizer
    path: visualizer-node/visualizer_node/main.py
    inputs:
      data: data_processor/temp_smoothed

编译运行(MacOS M1)

bash 复制代码
# 1. python项目依赖管理
cd visualizer-node && uv install

# 2. 激活虚拟环境, 在dora up 之前 ,否则 会报错:找不到python路径
source visualizer-node/.venv/bin/activate

# 3. 构建所有Rust节点(M1编译飞快)
dora build dataflow.yml

#4. 启动守护进程
dora up

# 5. 运行数据流
dora start dataflow.yml

# 6. 在另一个终端查看状态
dora list

预期输出

bash 复制代码
2025-12-12T10:33:47.988647Z  INFO dora_core::descriptor::validate: skipping path check for node with build command
2025-12-12T10:33:47.988649Z  INFO dora_core::descriptor::validate: skipping path check for node with build command
2025-12-12T10:33:47.991297Z  INFO zenoh::net::runtime: Using ZID: 8b8a68bebfc1f776ff2fd1a5a0d3622c
2025-12-12T10:33:47.998142Z  INFO zenoh::net::runtime::orchestrator: Zenoh can be reached at: tcp/[fe80::1]:57716
......
025-12-12T10:33:47.998392Z  INFO zenoh::net::runtime::orchestrator: zenohd listening scout messages on 224.0.0.224:7446
data_processor: DEBUG  daemon::spawner    spawning node
logger: DEBUG  daemon::spawner    spawning node
temp_sensor: DEBUG  daemon::spawner    spawning node
visualizer: DEBUG  daemon::spawner    spawning node
......
data_processor: INFO   daemon    node is ready
logger: INFO   daemon    node is ready
temp_sensor: INFO   daemon    node is ready
visualizer: INFO   daemon    node is ready
INFO   daemon    all nodes are ready, starting dataflow
temp_sensor: stdout    🌡️ 传感器节点启动 (M1 Pro优化版)
data_processor: stdout    🧮 处理器节点启动 (滑动窗口+异常检测)
logger: stdout    日志节点启动
visualizer: stdout      ani = animation.FuncAnimation(
[23.1°C] ██████████████████████████████████████████████
...

运行效果

GitHub仓库github.com/Doomking/ru...


三 核心要点总结

dora-rs 的价值 在于,它将构建复杂实时系统的架构复杂性业务逻辑复杂性解耦。开发者可以更专注于每个节点的核心算法,而将节点间通信、数据序列化、生命周期管理等繁琐问题交给框架。

概念 一句话理解 实战技巧
Dataflow 应用的"施工图" YAML 定义,先画后写代码
Node 独立工人进程 跨语言混编,故障隔离
Operator 工人手里的工具 单进程,适合高频计算
Event Stream 永不阻塞的收件箱 for event in node阻塞式,CPU零空转
Arrow Data 零拷贝魔法 > 4KB 自动走共享内存,M1统一内存架构更优
API绑定 多语言全家桶 Rust性能最好,Python生态最丰富
CLI 指挥中心三剑客 buildstartlist
Daemon 单机车间主任 本地共享内存管理器
Coordinator 分布式总经理 跨机器协调,支持ROS2级复杂系统

通过这个实战项目,我们体验了 dora-rs 的核心工作流程:

  1. 蓝图设计 :用 YAML 定义清晰的数据流拓扑。
  2. 异构开发:自由选择 Python(传感器、绘图)和 Rust(高性能计算)实现节点。
  3. 高效通信 :底层基于 Arrow 的零拷贝机制,让跨语言数据交换毫无负担。
  4. 统一运行 :一个 dora start 命令拉起所有组件,并按蓝图自动连接。

📖 参考资料

相关推荐
前端开发工程师请求出战2 小时前
把大模型装进口袋:HuggingFace如何让AI民主化成为现实
人工智能
亚马逊云开发者2 小时前
Amazon Connect结合Strands框架及Bedrock Agent Core的智能客服机器人解决方案(实践篇)
人工智能
2401_834517072 小时前
AD学习笔记-36 gerber文件输出
笔记·学习
hhhhhhh_hhhhhh_2 小时前
TC3x7-DEMO-V1.0原理图自学笔记
笔记
气π2 小时前
【JavaWeb】——(若依 + AI)-基础学习笔记
java·spring boot·笔记·学习·java-ee·mybatis·ruoyi
谷歌开发者2 小时前
Web 开发指向标|AI 辅助功能在性能面板中的使用与功能
前端·人工智能
深蓝海拓2 小时前
PySide6从0开始学习的笔记(三) 布局管理器与尺寸策略
笔记·python·qt·学习·pyqt
暗然而日章2 小时前
C++基础:Stanford CS106L学习笔记 8 继承
c++·笔记·学习
2401_834517072 小时前
AD学习笔记-34 PCBlogo的添加
笔记·学习