DoraMate 项目(12) - 数据模型设计详解: Rust 全栈类型系统与文件系统架构
DoraMate 项目(12) - 数据模型设计详解: Rust 全栈类型系统与文件系统架构
源起之道支持|Supported by Upstream Labs
本文详细介绍 DoraMate 的数据模型设计,包括前端数据结构、后端 API 模型、文件系统架构、数据转换器、验证策略等,展示如何使用 Rust 类型系统构建类型安全的数据模型。
文章目录
前言
数据模型是应用的基石。DoraMate 采用 Rust 全栈 + 纯文件系统架构,通过精心设计的数据结构实现类型安全、零配置部署和版本控制友好的数据持久化方案。本文将详细介绍我们的数据模型设计,包括前端 Leptos 数据结构、后端 Axum API 模型、文件系统数据格式、双向转换器等。
一、前端数据结构设计
1.1 核心数据模型
文件: doramate-frontend/src/types.rs
Dataflow - 数据流图
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// 数据流图 (DoraMate 内部格式 - 用于可视化编辑器)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Dataflow {
pub nodes: Vec<Node>,
pub connections: Vec<Connection>,
}
/// DORA 数据流图 (运行时格式 - 用于 dora-runtime)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct DoraDataflow {
/// DoraMate 扩展元数据 (可选)
#[serde(skip_serializing_if = "Option::is_none")]
pub __doramate__: Option<DoraMateMeta>,
/// 节点列表
pub nodes: Vec<DoraNode>,
}
/// DoraMate 元数据 (存储可视化信息)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct DoraMateMeta {
/// 布局信息 (节点位置、标签等)
#[serde(skip_serializing_if = "Option::is_none")]
pub layout: Option<HashMap<String, LayoutInfo>>,
/// 数据流名称
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// 数据流描述
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// 标签
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
}
/// 布局信息 (节点在画布上的位置)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct LayoutInfo {
/// X 坐标
pub x: f64,
/// Y 坐标
pub y: f64,
/// 显示标签 (可选)
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
设计要点:
- 双格式支持:
Dataflow(可视化编辑器) 和DoraDataflow(DORA 运行时) - 元数据分离: 通过
__doramate__字段存储可视化信息,不影响 DORA 运行 - 可选字段: 使用
Option<T>和skip_serializing_if保持 YAML 简洁
Node - 节点数据结构
/// 节点 (DoraMate 可视化编辑器格式)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Node {
/// 节点唯一标识符
pub id: String,
/// X 坐标 (可视化位置)
pub x: f64,
/// Y 坐标 (可视化位置)
pub y: f64,
/// 显示标签
pub label: String,
/// 节点类型 (用于推断 DORA path 和 build)
#[serde(rename = "type")]
pub node_type: String,
/// 节点路径 (可选,用于自定义 DORA node 路径)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
/// 环境变量 (可选)
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
/// 自定义配置 (可选)
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<serde_yaml::Value>,
/// 输出端口列表 (可选,用于可视化)
#[serde(skip_serializing_if = "Option::is_none")]
pub outputs: Option<Vec<String>>,
/// 输入端口列表 (可选,用于可视化)
#[serde(skip_serializing_if = "Option::is_none")]
pub inputs: Option<Vec<String>>,
/// 节点缩放比例 (可选,用于可视化,默认 1.0)
#[serde(skip_serializing_if = "Option::is_none")]
pub scale: Option<f64>,
}
设计要点:
- 类型安全: 使用 Rust 类型系统保证编译时检查
- 灵活扩展:
Option<T>支持可选字段 - 动态配置:
HashMap<String, serde_yaml::Value>支持任意配置 - 关键字处理:
#[serde(rename = "type")]避免 Rust 关键字冲突
Connection - 连线
/// 连线 (DoraMate 可视化格式)
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Connection {
/// 源节点 ID
pub from: String,
/// 目标节点 ID
pub to: String,
/// 输出端口名称 (可选,默认为 "out")
#[serde(skip_serializing_if = "Option::is_none")]
pub from_port: Option<String>,
/// 输入端口名称 (可选,默认为 "in")
#[serde(skip_serializing_if = "Option::is_none")]
pub to_port: Option<String>,
}
设计要点:
- 端口可选: 默认端口使用
Option处理 - 简洁输出:
skip_serializing_if避免冗余字段
状态类型定义
/// 端口类型
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PortType {
Input,
Output,
}
/// 节点运行状态
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum NodeState {
/// 空闲状态
Idle,
/// 启动中
Starting,
/// 运行中
Running,
/// 已停止
Stopped,
/// 错误状态
Error(String),
}
impl NodeState {
/// 获取状态对应的CSS类名
pub fn css_class(&self) -> &'static str {
match self {
NodeState::Idle => "node-idle",
NodeState::Starting => "node-starting",
NodeState::Running => "node-running",
NodeState::Stopped => "node-stopped",
NodeState::Error(_) => "node-error",
}
}
/// 获取状态对应的边框颜色
pub fn border_color(&self) -> &'static str {
match self {
NodeState::Idle => "#2196F3", // 蓝色
NodeState::Starting => "#FF9800", // 橙色
NodeState::Running => "#4CAF50", // 绿色
NodeState::Stopped => "#9E9E9E", // 灰色
NodeState::Error(_) => "#f44336", // 红色
}
}
/// 获取状态显示文本
pub fn display_text(&self) -> &'static str {
match self {
NodeState::Idle => "空闲",
NodeState::Starting => "启动中",
NodeState::Running => "运行中",
NodeState::Stopped => "已停止",
NodeState::Error(_) => "错误",
}
}
}
设计要点:
- Enum 模式匹配: 使用
match表达式处理不同状态 - 方法封装: 为 Enum 提供便捷方法
- 类型安全: 编译时保证所有状态都被处理
二、节点注册表数据模型
2.1 NodeDefinition - 节点定义
文件: doramate-frontend/src/node_registry.rs
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// 节点定义 - 描述一个节点类型的所有元数据
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NodeDefinition {
/// 节点唯一标识符
pub id: String,
/// 节点显示名称
pub name: String,
/// 节点描述
pub description: String,
/// 节点分类
pub category: NodeCategory,
/// 节点类型 (对应 DORA node type)
pub node_type: String,
/// 节点图标 (emoji)
pub icon: String,
/// 节点路径 (可选,用于自定义 DORA node 路径)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
/// 构建命令 (可选,用于编译自定义节点)
#[serde(skip_serializing_if = "Option::is_none")]
pub build: Option<String>,
/// 默认环境变量
#[serde(skip_serializing_if = "Option::is_none")]
pub default_env: Option<HashMap<String, String>>,
/// 默认配置
#[serde(skip_serializing_if = "Option::is_none")]
pub default_config: Option<HashMap<String, serde_yaml::Value>>,
/// 输入端口定义
#[serde(skip_serializing_if = "Option::is_none")]
pub inputs: Option<Vec<PortDefinition>>,
/// 输出端口定义
#[serde(skip_serializing_if = "Option::is_none")]
pub outputs: Option<Vec<PortDefinition>>,
/// 可配置参数
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<Vec<ParameterDefinition>>,
}
2.2 PortDefinition - 端口定义
/// 端口定义
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PortDefinition {
/// 端口名称
pub name: String,
/// 端口类型
#[serde(rename = "type")]
pub port_type: PortDataType,
/// 端口描述
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// 是否必需
#[serde(default = "default_true")]
pub required: bool,
}
fn default_true() -> bool {
true
}
/// 端口数据类型
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum PortDataType {
/// 任意类型
Any,
/// 图像数据
Image,
/// 文本/字符串
Text,
/// JSON 数据
Json,
/// 音频数据
Audio,
/// 视频数据
Video,
/// 数值数组
Array,
/// 自定义类型
Custom(String),
}
2.3 NodeCategory - 节点分类
/// 节点分类
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum NodeCategory {
/// 输入节点 (摄像头、麦克风、传感器等)
Input,
/// 处理节点 (AI模型、图像处理、数据转换等)
Process,
/// 输出节点 (显示器、录制、API调用等)
Output,
/// 自定义节点 (用户自定义节点)
Custom,
}
impl NodeCategory {
/// 获取分类对应的显示名称
pub fn display_name(&self) -> &'static str {
match self {
NodeCategory::Input => "输入",
NodeCategory::Process => "处理",
NodeCategory::Output => "输出",
NodeCategory::Custom => "自定义",
}
}
/// 获取分类对应的颜色
pub fn color(&self) -> &'static str {
match self {
NodeCategory::Input => "#4CAF50", // 绿色
NodeCategory::Process => "#2196F3", // 蓝色
NodeCategory::Output => "#FF9800", // 橙色
NodeCategory::Custom => "#9C27B0", // 紫色
}
}
}
三、后端数据模型 (Axum)
3.1 API 请求/响应模型
文件: doramate-localagent/src/main.rs
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// 运行数据流请求
#[derive(Deserialize, Debug)]
pub struct RunDataflowRequest {
/// 数据流 YAML 内容
pub dataflow_yaml: String,
/// 工作目录 (可选)
pub working_dir: Option<String>,
}
/// 运行数据流响应
#[derive(Serialize)]
pub struct RunDataflowResponse {
/// 是否成功
pub success: bool,
/// 消息
pub message: String,
/// 进程 ID (如果成功)
pub process_id: Option<String>,
}
/// 停止数据流请求
#[derive(Deserialize, Debug)]
pub struct StopDataflowRequest {
/// 进程 ID
pub process_id: String,
}
/// 停止数据流响应
#[derive(Serialize)]
pub struct StopDataflowResponse {
/// 是否成功
pub success: bool,
/// 消息
pub message: String,
}
/// 健康检查响应
#[derive(Serialize)]
pub struct HealthResponse {
/// 状态
pub status: String,
/// 版本号
pub version: String,
/// DORA 是否已安装
pub dora_installed: bool,
}
/// 数据流状态响应
#[derive(Serialize)]
pub struct DataflowStatusResponse {
/// 进程 ID
pub process_id: String,
/// 状态 ("running" | "stopped" | "not_found")
pub status: String,
/// 运行时间 (秒)
pub uptime_seconds: u64,
}
3.2 进程管理模型
use std::sync::{Arc, Mutex};
use tokio::process::Child;
use uuid::Uuid;
/// 应用状态 (存储运行的进程)
#[derive(Clone)]
struct AppState {
/// 进程映射 (process_id -> DoraProcess)
processes: Arc<Mutex<HashMap<String, DoraProcess>>>,
}
impl AppState {
fn new() -> Self {
Self {
processes: Arc::new(Mutex::new(HashMap::new())),
}
}
}
/// DORA 进程信息
#[derive(Clone, Debug)]
struct DoraProcess {
/// 进程 ID
id: String,
/// YAML 文件路径
yaml_path: String,
/// 子进程句柄
child: Arc<Mutex<Option<Child>>>,
}
设计要点:
- 线程安全: 使用
Arc<Mutex<>>保证多线程安全 - 进程隔离: 每个数据流独立进程
- UUID 标识: 使用 UUID 保证唯一性
四、文件系统数据模型
4.1 数据流文件格式
文件: ~/.doramate/dataflows/my-flow.yml
# DoraMate 扩展元数据 (可选)
__doramate__:
name: "My Camera Flow"
description: "Camera capture with YOLO detection"
tags: [vision, yolo]
layout:
camera:
x: 100
y: 100
label: "Camera"
yolo:
x: 400
y: 100
label: "YOLO"
# 标准 DORA 数据流定义
nodes:
camera:
id: camera
path: ../nodes/camera-opencv
inputs:
timer:
source: dora/timer/millis/30
interval: 30
yolo:
id: yolo
path: ../nodes/yolov8
inputs:
image:
source: camera
queue_size: 10
设计要点:
- 元数据分离:
__doramate__字段存储可视化信息 - 向后兼容: DORA 运行时忽略未知字段
- 人类可读: YAML 格式易于阅读和编辑
4.2 最近文件列表
文件: ~/.doramate/recent.json
{
"recent": [
{
"path": "~/projects/flow.yml",
"opened_at": "2025-01-21T10:00:00Z"
},
{
"path": "~/.doramate/dataflows/test.yml",
"opened_at": "2025-01-21T09:30:00Z"
}
]
}
数据模型:
/// 最近文件条目
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct RecentFileEntry {
/// 文件路径
pub path: String,
/// 打开时间
pub opened_at: String, // ISO 8601 格式
}
/// 最近文件列表
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct RecentFiles {
pub recent: Vec<RecentFileEntry>,
}
4.3 配置文件
文件: ~/.doramate/config.toml
[editor]
theme = "dark" # dark | light
auto_save = true
auto_save_interval = 60 # seconds
font_size = 14
font_family = "JetBrains Mono"
[canvas]
grid_enabled = true
grid_size = 20
snap_to_grid = true
zoom_sensitivity = 1.2
[files]
default_folder = "~/.doramate/dataflows"
show_hidden = false
auto_backup = true
backup_count = 10
[runtime]
dora_path = "/usr/bin/dora" # or "C:\Program Files\DORA\dora.exe"
log_level = "info" # debug | info | warn | error
max_log_size = 10485760 # 10MB
[proxy]
enabled = true
host = "localhost"
port = 52100
数据模型:
use serde::{Deserialize, Serialize};
/// DoraMate 配置
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Config {
pub editor: EditorConfig,
pub canvas: CanvasConfig,
pub files: FilesConfig,
pub runtime: RuntimeConfig,
pub proxy: ProxyConfig,
}
/// 编辑器配置
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct EditorConfig {
pub theme: String,
pub auto_save: bool,
pub auto_save_interval: u64,
pub font_size: u32,
pub font_family: String,
}
/// 画布配置
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct CanvasConfig {
pub grid_enabled: bool,
pub grid_size: u32,
pub snap_to_grid: bool,
pub zoom_sensitivity: f64,
}
/// 文件配置
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct FilesConfig {
pub default_folder: String,
pub show_hidden: bool,
pub auto_backup: bool,
pub backup_count: usize,
}
/// 运行时配置
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct RuntimeConfig {
pub dora_path: String,
pub log_level: String,
pub max_log_size: u64,
}
/// 代理配置
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ProxyConfig {
pub enabled: bool,
pub host: String,
pub port: u16,
}
五、数据转换器
5.1 YAML ↔ 可视化图 转换
文件: doramate-frontend/src/utils/converter.rs
use crate::types::{Dataflow, DoraDataflow, Node, Connection, DoraNode, DoraMateMeta, LayoutInfo};
use std::collections::HashMap;
/// 可视化图 → DORA YAML
impl From<&Dataflow> for DoraDataflow {
fn from(dataflow: &Dataflow) -> Self {
let mut layout = HashMap::new();
let mut nodes = Vec::new();
// 转换节点
for node in &dataflow.nodes {
// 保存布局信息
layout.insert(
node.id.clone(),
LayoutInfo {
x: node.x,
y: node.y,
label: Some(node.label.clone()),
},
);
// 推断 DORA path 和 build
let (path, build) = infer_node_path_and_build(&node.node_type);
// 构建 DORA 节点
let mut dora_node = DoraNode {
id: node.id.clone(),
path: Some(path),
build,
inputs: None,
outputs: node.outputs.clone(),
env: node.env.clone(),
operators: None,
};
// 处理输入连接
let mut inputs = HashMap::new();
for conn in &dataflow.connections {
if conn.to == node.id {
let from_port = conn.from_port.as_deref().unwrap_or("out");
let to_port = conn.to_port.as_deref().unwrap_or("in");
inputs.insert(to_port.to_string(), format!("{}:{}", conn.from, from_port));
}
}
if !inputs.is_empty() {
dora_node.inputs = Some(inputs);
}
nodes.push(dora_node);
}
DoraDataflow {
__doramate__: Some(DoraMateMeta {
layout: Some(layout),
name: None,
description: None,
tags: None,
}),
nodes,
}
}
}
转换要点:
- 保留布局信息: 通过
__doramate__.layout保存可视化位置 - 自动推断: 根据节点类型推断 DORA path 和 build
- 连接关系生成: 从
Connection生成 DORA inputs 映射
六、数据验证
6.1 前端验证 (Leptos)
/// 验证数据流
pub fn validate_dataflow(dataflow: &Dataflow) -> Vec<ValidationError> {
let mut errors = Vec::new();
// 检查节点 ID 唯一性
let mut ids = std::collections::HashSet::new();
for node in &dataflow.nodes {
if !ids.insert(&node.id) {
errors.push(ValidationError {
code: "DUPLICATE_NODE_ID".to_string(),
message: format!("重复的节点 ID: {}", node.id),
severity: ErrorSeverity::Error,
});
}
}
// 检查连接的有效性
for conn in &dataflow.connections {
let from_exists = dataflow.nodes.iter().any(|n| n.id == conn.from);
let to_exists = dataflow.nodes.iter().any(|n| n.id == conn.to);
if !from_exists {
errors.push(ValidationError {
code: "SOURCE_NODE_NOT_FOUND".to_string(),
message: format!("源节点不存在: {}", conn.from),
severity: ErrorSeverity::Error,
});
}
if !to_exists {
errors.push(ValidationError {
code: "TARGET_NODE_NOT_FOUND".to_string(),
message: format!("目标节点不存在: {}", conn.to),
severity: ErrorSeverity::Error,
});
}
}
// 检查循环依赖
if has_cycles(dataflow) {
errors.push(ValidationError {
code: "CYCLIC_DEPENDENCY".to_string(),
message: "数据流存在循环依赖".to_string(),
severity: ErrorSeverity::Error,
});
}
errors
}
验证策略:
- 唯一性检查: 节点 ID 不能重复
- 完整性检查: 连接的源节点和目标节点必须存在
- 循环检测: 使用 DFS 算法检测循环依赖
七、数据模型对比
7.1 与 Blazor/C# 版本对比
| 数据结构 | Rust 版本 | Blazor 版本 | 优势 |
|---|---|---|---|
| 节点 | Node (Struct) |
GraphNode (Class) |
Rust: 零成本抽象、栈分配 |
| 连接 | Connection (Struct) |
Connection (Class) |
Rust: 不可变、线程安全 |
| 状态 | NodeState (Enum) |
NodeStatus (Enum) |
Rust: 模式匹配、编译时检查 |
| 元数据 | Option<T> |
nullable | Rust: 编译时空值检查 |
| 序列化 | serde |
System.Text.Json |
Rust: 零拷贝、类型安全 |
| 验证 | validator + 手动 |
DataAnnotations |
Rust: 编译时验证、零开销 |
7.2 纯文件系统 vs 数据库对比 ⭐
| 维度 | 纯文件系统 (Rust 版) | 数据库 (Blazor 版) | 优势 |
|---|---|---|---|
| 配置复杂度 | ⭐⭐⭐⭐⭐ (零配置) | ⭐⭐ (需要 PostgreSQL) | 文件系统 |
| 依赖 | ⭐⭐⭐⭐⭐ (仅 serde_yaml) | ⭐⭐ (SeaORM + 驱动) | 文件系统 |
| 备份 | ⭐⭐⭐⭐⭐ (复制文件夹) | ⭐⭐⭐ (需要工具) | 文件系统 |
| 可读性 | ⭐⭐⭐⭐⭐ (YAML 人性化) | ⭐⭐ (需要查询) | 文件系统 |
| 版本控制 | ⭐⭐⭐⭐⭐ (Git 友好) | ⭐⭐⭐ (需要迁移) | 文件系统 |
| 查询性能 | ⭐⭐⭐ (线性扫描) | ⭐⭐⭐⭐⭐ (索引) | 数据库 |
| 并发安全 | ⭐⭐⭐⭐ (文件锁) | ⭐⭐⭐⭐⭐ (事务) | 数据库 |
| 扩展性 | ⭐⭐⭐ (单机) | ⭐⭐⭐⭐⭐ (分布式) | 数据库 |
为什么选择纯文件系统? ⭐:
- ✅ 零配置: 用户无需安装、配置数据库
- ✅ 简化开发: 专注核心功能,加速 MVP
- ✅ 可读性: YAML 文件可直接阅读和编辑
- ✅ 版本控制: Git 友好,易于协作
- ✅ 符合 DORA 哲学: YAML 即配置
- ✅ 降低学习曲线: 文件系统比数据库更直观
八、最佳实践
8.1 数据结构设计原则
-
优先使用 Struct 而不是 Enum
- Struct 提供更灵活的扩展性
- Enum 用于固定的状态类型
-
使用 Option 表示可选字段
- 避免 null 值
- 编译时保证类型安全
-
使用 HashMap 存储动态配置
HashMap<String, String>用于环境变量HashMap<String, serde_yaml::Value>用于动态配置
-
添加
#[serde(skip_serializing_if = "Option::is_none")]- 避免 YAML 中出现大量 null 值
- 保持输出简洁
-
使用
#[serde(rename = "type")]处理关键字- 避免与 Rust 关键字冲突
- 保持 YAML 格式标准
8.2 数据转换最佳实践
-
实现
From<T>Trait- 提供零成本转换
- 类型安全保证
-
使用
Option处理可选字段- 避免 panic
- 优雅降级
-
提供合理的默认值
- 使用
infer_*函数推断 - 保持向后兼容
- 使用
-
验证数据完整性
- 检查循环依赖
- 验证连接有效性
九、总结
9.1 核心设计原则
类型安全 ⭐⭐⭐⭐⭐
- Rust 类型系统保证编译时检查
- 零运行时类型错误
- 智能提示完备
零配置 ⭐⭐⭐⭐⭐
- 纯文件系统架构
- 无需数据库安装
- 开箱即用
可读性 ⭐⭐⭐⭐⭐
- YAML 人类可读
- Git 友好
- 易于协作
向后兼容 ⭐⭐⭐⭐⭐
- DORA 运行时忽略未知字段
- 渐进式增强
- 平滑升级
9.2 技术优势
| 特性 | Rust 版本 | 传统方案 | 提升 |
|---|---|---|---|
| 类型安全 | ⭐⭐⭐⭐⭐ (编译时) | ⭐⭐⭐ (运行时) | 100% |
| 性能 | ⭐⭐⭐⭐⭐ (零拷贝) | ⭐⭐⭐ (序列化开销) | 10x |
| 配置 | ⭐⭐⭐⭐⭐ (零配置) | ⭐⭐ (需数据库) | ∞ |
| 可维护性 | ⭐⭐⭐⭐⭐ (类型推导) | ⭐⭐⭐ (手动维护) | 5x |
十、下一步
📖 第十三章: 验收标准详解 - 当前版本应该如何定义“可交付”
📖 第十四章: 本地执行架构设计详解 - Rust 全栈实现工业级本地代理服务
源起之道支持|Supported by Upstream Labs
日期: 2025-03-13
系列: DoraMate 项目技术博客系列
上一篇: 11-MVP开发计划详解 - 从6个月路线图到Rust全栈项目MVP收口
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)