目录
可以。结合你后续要做的 Salvo + SeaORM + MySQL + Redis + RBAC,并且还会包含前端,我建议采用:
单仓库 Monorepo + Cargo Workspace + 多应用入口 + 业务模块化
先不要一开始就拆成微服务。目录上支持多个应用,但初期仍可只部署一个 API 服务,等业务成熟后再拆分。
Cargo Workspace 可以统一管理多个 Rust package,共享根目录的 Cargo.lock、target、依赖版本和构建命令,非常适合这种多应用项目。对于没有根 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、定时任务、用户门户或拆分独立服务预留了空间。