不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-17 22:54:10  修改时间:2026-07-22 21:56:10  分类:01.Rust编程  编辑

目录

可以。结合你后续要做的 Salvo + SeaORM + MySQL + Redis + RBAC,并且还会包含前端,我建议采用:

单仓库 Monorepo + Cargo Workspace + 多应用入口 + 业务模块化

先不要一开始就拆成微服务。目录上支持多个应用,但初期仍可只部署一个 API 服务,等业务成熟后再拆分。

Cargo Workspace 可以统一管理多个 Rust package,共享根目录的 Cargo.locktarget、依赖版本和构建命令,非常适合这种多应用项目。对于没有根 package 的虚拟 Workspace,Rust 2024 项目应显式设置 resolver = "3"。(Rust 文档)

一、推荐的整体目录

假设项目叫:

fuyo-workbench

建议目录如下:

fuyo-workbench/
├── Cargo.toml                  # Rust Workspace 根配置
├── Cargo.lock
├── rust-toolchain.toml         # 固定 Rust 工具链
├── .env.example
├── .gitignore
├── README.md
│
├── apps/                       # 可独立启动、部署的 Rust 应用
│   ├── api-server/             # 主 HTTP API 服务
│   │   ├── Cargo.toml
│   │   └── src/
│   │       ├── main.rs
│   │       ├── bootstrap.rs
│   │       ├── router.rs
│   │       └── state.rs
│   │
│   ├── worker/                 # 异步任务消费者
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   │
│   ├── scheduler/              # 定时任务服务
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── main.rs
│   │
│   └── migration-cli/         # 数据库迁移命令行程序
│       ├── Cargo.toml
│       └── src/
│           └── main.rs
│
├── crates/                     # 通用技术基础库
│   ├── app-core/               # 应用启动、AppState、生命周期
│   ├── common/                 # 通用类型、分页、时间、ID等
│   ├── config/                 # 配置加载
│   ├── error/                  # 统一错误定义
│   ├── persistence/            # MySQL、SeaORM、Redis
│   ├── web-core/               # Salvo通用中间件、响应结构
│   ├── auth/                   # JWT、认证上下文、密码处理
│   ├── observability/          # 日志、Tracing、指标
│   └── migration/              # SeaORM Migration
│
├── modules/                    # 业务模块
│   ├── identity/               # 用户、角色、权限、组织
│   ├── ticketing/              # 工单管理
│   ├── approval/               # 审批流程
│   ├── asset/                  # IT资产管理
│   ├── notification/           # 邮件、企业微信、站内通知
│   └── system/                 # 字典、参数、日志等系统功能
│
├── web/                        # 前端工程
│   ├── admin-web/              # 运维管理后台
│   ├── portal-web/             # 用户工单门户
│   └── packages/               # 前端共享包
│       ├── api-client/         # API请求封装
│       ├── ui/                 # 公共UI组件
│       ├── types/              # TypeScript公共类型
│       └── utils/              # 前端工具函数
│
├── config/                     # 不同环境配置文件
│   ├── development.toml
│   ├── test.toml
│   └── production.toml
│
├── deploy/                     # 部署相关
│   ├── docker/
│   ├── nginx/
│   ├── systemd/
│   └── kubernetes/
│
├── scripts/                    # 初始化、构建、部署脚本
│   ├── dev.ps1
│   ├── dev.sh
│   ├── build.ps1
│   └── migrate.ps1
│
├── docs/                       # 设计文档
│   ├── architecture/
│   ├── database/
│   ├── api/
│   └── deployment/
│
└── tests/                      # 跨模块集成测试
    ├── api/
    └── fixtures/

二、核心目录如何理解

1. apps:可运行程序

apps 中的每一个目录,都是一个带有 main.rs 的二进制 crate。

例如:

apps/
├── api-server
├── worker
├── scheduler
└── migration-cli

它们的特点是:

  • 可以单独启动
  • 可以单独构建
  • 可以单独部署
  • 自身尽量不写复杂业务逻辑
  • 主要负责组装配置、数据库、路由和业务模块

例如运行 API 服务:

cargo run -p api-server

运行异步任务:

cargo run -p worker

运行定时任务:

cargo run -p scheduler

2. crates:通用技术能力

crates 放与具体工单、资产等业务无关的通用能力。

例如:

crates/
├── config
├── error
├── persistence
├── auth
├── web-core
└── observability

这里不要出现类似:

create_ticket
approve_ticket
asset_inventory

这些属于业务代码,应该放到 modules

3. modules:业务功能

每个业务模块都是一个独立 library crate:

modules/
├── identity
├── ticketing
├── approval
├── asset
├── notification
└── system

这种结构特别适合你的 IT 运维系统:

模块 主要功能
identity 用户、部门、岗位、角色、权限
ticketing 工单创建、分派、处理、关闭
approval 审批流、审批节点、审批记录
asset 服务器、虚拟机、网络设备、业务系统
notification 邮件、企业微信、站内消息
system 字典、系统参数、操作日志

三、单个业务模块的内部结构

以工单模块为例:

modules/ticketing/
├── Cargo.toml
└── src/
    ├── lib.rs
    │
    ├── domain/                 # 核心业务模型和规则
    │   ├── mod.rs
    │   ├── ticket.rs
    │   ├── ticket_status.rs
    │   └── repository.rs
    │
    ├── application/            # 用例、业务流程
    │   ├── mod.rs
    │   ├── create_ticket.rs
    │   ├── assign_ticket.rs
    │   ├── close_ticket.rs
    │   └── query_ticket.rs
    │
    ├── infrastructure/         # 数据库、Redis等具体实现
    │   ├── mod.rs
    │   ├── ticket_repository.rs
    │   └── ticket_cache.rs
    │
    └── presentation/           # Salvo HTTP接口
        ├── mod.rs
        ├── dto/
        │   ├── mod.rs
        │   ├── create_ticket_request.rs
        │   └── ticket_response.rs
        ├── handler/
        │   ├── mod.rs
        │   ├── create.rs
        │   ├── detail.rs
        │   ├── list.rs
        │   └── close.rs
        └── router.rs

调用关系建议保持为:

presentation
      ↓
application
      ↓
domain
      ↑
infrastructure

也就是:

Salvo Handler
    ↓
Application Service
    ↓
Domain Model / Repository Trait
    ↓
SeaORM Repository Implementation

这样 Salvo Handler 不会直接堆积数据库查询和复杂业务判断。

四、根目录 Cargo.toml

建议根目录只作为虚拟 Workspace,不作为可执行程序:

[workspace]
resolver = "3"
members = [
    "apps/*",
    "crates/*",
    "modules/*",
]
default-members = [
    "apps/api-server",
]

[workspace.package]
version = "0.1.0"
edition = "2024"
publish = false

[workspace.dependencies]
# 内部公共 crate
app-core = { path = "crates/app-core" }
common = { path = "crates/common" }
config = { path = "crates/config" }
error = { path = "crates/error" }
persistence = { path = "crates/persistence" }
web-core = { path = "crates/web-core" }
auth = { path = "crates/auth" }
observability = { path = "crates/observability" }
migration = { path = "crates/migration" }

# 内部业务模块
identity = { path = "modules/identity" }
ticketing = { path = "modules/ticketing" }
approval = { path = "modules/approval" }
asset = { path = "modules/asset" }
notification = { path = "modules/notification" }
system = { path = "modules/system" }

Cargo 官方支持通过 members 的通配符管理多个成员,也支持使用 [workspace.package][workspace.dependencies] 统一继承元数据与依赖。(Rust 文档)

外部依赖也建议集中放在根目录,例如:

[workspace.dependencies]
salvo = { version = "填写当前版本", features = [
    "oapi",
    "cors",
    "jwt-auth",
    "logging",
    "affix-state",
] }

tokio = { version = "填写当前版本", features = ["full"] }
sea-orm = { version = "填写当前版本" }
serde = { version = "填写当前版本", features = ["derive"] }
thiserror = "填写当前版本"
anyhow = "填写当前版本"
tracing = "填写当前版本"

成员 crate 中只需要继承:

[dependencies]
salvo.workspace = true
tokio.workspace = true
serde.workspace = true

五、api-server 应用结构

apps/api-server/src/
├── main.rs
├── bootstrap.rs
├── router.rs
└── state.rs

main.rs

只负责启动:

use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    observability::init_logging()?;

    let app = bootstrap::build_application().await?;
    app.run().await?;

    Ok(())
}

bootstrap.rs

负责组装应用:

pub async fn build_application() -> anyhow::Result<Application> {
    // 1. 加载配置
    // 2. 创建 MySQL 连接
    // 3. 创建 Redis 连接
    // 4. 创建 AppState
    // 5. 创建 Salvo Router
    // 6. 返回 Application

    todo!()
}

router.rs

负责聚合所有业务路由:

use salvo::prelude::*;

pub fn create_router() -> Router {
    Router::new()
        .push(
            Router::with_path("api/v1")
                .push(identity::presentation::router())
                .push(ticketing::presentation::router())
                .push(approval::presentation::router())
                .push(asset::presentation::router())
                .push(system::presentation::router()),
        )
}

Salvo 的 Router 支持嵌套路由;添加到某个 Router 上的 hoop 中间件会影响该 Router 以及它的子路由,因此可以很自然地实现全局认证和模块级权限校验。(salvo.rs)

例如:

pub fn create_router() -> Router {
    let public_router = Router::with_path("api/v1")
        .push(identity::presentation::public_router());

    let protected_router = Router::with_path("api/v1")
        .hoop(auth::JwtAuth)
        .push(ticketing::presentation::router())
        .push(asset::presentation::router())
        .push(approval::presentation::router());

    Router::new()
        .push(public_router)
        .push(protected_router)
}

六、业务模块路由示例

// modules/ticketing/src/presentation/router.rs

use salvo::prelude::*;

use super::handler;

pub fn router() -> Router {
    Router::with_path("tickets")
        .get(handler::list)
        .post(handler::create)
        .push(
            Router::with_path("{ticket_id}")
                .get(handler::detail)
                .put(handler::update),
        )
        .push(
            Router::with_path("{ticket_id}/close")
                .post(handler::close),
        )
}

接口最终类似:

GET    /api/v1/tickets
POST   /api/v1/tickets
GET    /api/v1/tickets/{ticket_id}
PUT    /api/v1/tickets/{ticket_id}
POST   /api/v1/tickets/{ticket_id}/close

Salvo 官方提供 OpenAPI 集成,可以通过 #[endpoint] 提取接口信息,再挂载 Swagger UI 或其他文档界面。(salvo.rs)

七、统一应用状态 AppState

建议把数据库、Redis、配置等放入统一状态:

use std::sync::Arc;

#[derive(Clone)]
pub struct AppState {
    pub config: Arc<AppConfig>,
    pub database: sea_orm::DatabaseConnection,
    pub redis: RedisPool,
}

在启动时注入:

Router::new()
    .hoop(
        salvo::affix_state::inject(app_state)
    )

Salvo 的 Affix State 可以在路由阶段注入应用配置、数据库连接池、缓存客户端等共享对象,并在 Handler 中通过 Depot 获取。(salvo.rs)

但建议 Handler 不要自己从状态中拼装所有依赖。更规范的方式是:

pub struct AppState {
    pub ticket_service: Arc<TicketService>,
    pub user_service: Arc<UserService>,
    pub asset_service: Arc<AssetService>,
}

Handler 只调用服务:

#[handler]
pub async fn create(
    depot: &mut Depot,
    request: JsonBody<CreateTicketRequest>,
) -> Result<ApiResponse<TicketResponse>, AppError> {
    let state = depot.obtain::<AppState>()?;

    let ticket = state
        .ticket_service
        .create(request.into_inner())
        .await?;

    Ok(ApiResponse::success(ticket))
}

八、前端目录设计

如果只有一个管理后台,初期可以这样:

web/
└── admin-web/
    ├── package.json
    ├── vite.config.ts
    ├── tsconfig.json
    └── src/
        ├── api/
        ├── assets/
        ├── components/
        ├── layouts/
        ├── router/
        ├── stores/
        ├── utils/
        ├── views/
        ├── App.vue
        └── main.ts

如果未来存在:

  • 运维管理后台
  • 普通用户工单门户
  • 移动端页面
  • 公共 UI 组件
  • 公共 API SDK

则建议使用:

web/
├── admin-web/
├── portal-web/
└── packages/
    ├── api-client/
    ├── ui/
    ├── types/
    └── utils/

可以使用 pnpm Workspace 管理多个前端工程;pnpm 官方要求在 Workspace 根目录配置 pnpm-workspace.yaml。(pnpm)

根目录创建:

packages:
  - "web/*"
  - "web/packages/*"

注意排除不属于前端 package 的目录时,可以写得更明确:

packages:
  - "web/admin-web"
  - "web/portal-web"
  - "web/packages/*"

九、前后端接口类型管理

推荐由后端提供 OpenAPI:

Salvo endpoint
      ↓
openapi.json
      ↓
生成 TypeScript API Client
      ↓
web/packages/api-client

目录:

web/packages/api-client/
├── package.json
├── openapi.json
└── src/
    ├── generated/
    └── index.ts

这样前端不需要手工重复定义:

interface Ticket {
  id: number
  title: string
  status: string
}

避免后端字段改了,但前端类型没有同步。

十、创建目录的命令

在已经创建好的项目根目录执行:

mkdir apps crates modules web config deploy scripts docs tests

创建应用:

cargo new apps/api-server --bin --vcs none
cargo new apps/worker --bin --vcs none
cargo new apps/scheduler --bin --vcs none
cargo new apps/migration-cli --bin --vcs none

创建通用 crate:

cargo new crates/app-core --lib --vcs none
cargo new crates/common --lib --vcs none
cargo new crates/config --lib --vcs none
cargo new crates/error --lib --vcs none
cargo new crates/persistence --lib --vcs none
cargo new crates/web-core --lib --vcs none
cargo new crates/auth --lib --vcs none
cargo new crates/observability --lib --vcs none
cargo new crates/migration --lib --vcs none

创建业务模块:

cargo new modules/identity --lib --vcs none
cargo new modules/ticketing --lib --vcs none
cargo new modules/approval --lib --vcs none
cargo new modules/asset --lib --vcs none
cargo new modules/notification --lib --vcs none
cargo new modules/system --lib --vcs none

然后检查整个 Workspace:

cargo check --workspace

十一、现阶段不要拆得太细

建议第一阶段只实际建立:

apps/
└── api-server/

crates/
├── common/
├── config/
├── error/
├── persistence/
├── web-core/
├── auth/
└── migration/

modules/
├── identity/
├── ticketing/
└── system/

web/
└── admin-web/

下面几个等真正有需求时再创建:

apps/worker
apps/scheduler
modules/approval
modules/asset
modules/notification
web/portal-web

否则一开始就建立二三十个 crate,会增加依赖管理和代码跳转成本。

最终推荐原则

apps      = 运行入口
crates    = 通用技术能力
modules   = 业务功能
web       = 前端项目
deploy    = 部署配置
docs      = 项目文档

对于你的项目,最合适的起步组合是:

api-server
    ├── identity
    ├── ticketing
    ├── system
    ├── auth
    ├── persistence
    └── web-core

admin-web
    └── 调用 api-server

这套结构既能满足当前单体项目,也给以后增加 Worker、定时任务、用户门户或拆分独立服务预留了空间。