不灭的焱

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

作者:AlbertWen  添加时间:2026-07-02 09:57:36  修改时间:2026-07-07 20:19:35  分类:01.Rust编程  编辑

基于 Salvo 的项目架构,我建议不要按“框架目录”来分,而是按 业务模块 + 分层架构 来组织。Salvo 本身的核心比较轻:Router 负责路由树,Handler 既可以做接口处理器,也可以做中间件;中间件通过 Router::hoop() 挂载,并会影响当前路由及其子路由。(Salvo)

下面给你一套适合 后台 API / 管理系统 / IT 服务平台 / 运维平台 的项目结构。

一、推荐项目目录结构

salvo-admin-api/
├── Cargo.toml
├── .env
├── migrations/
│   ├── 202607020001_create_users.sql
│   └── 202607020002_create_tickets.sql
├── src/
│   ├── main.rs                 # 程序入口
│   ├── lib.rs                  # 模块导出,可选
│   │
│   ├── app/
│   │   ├── mod.rs              # 应用装配入口
│   │   ├── router.rs           # 总路由注册
│   │   ├── state.rs            # 全局状态 AppState
│   │   └── bootstrap.rs        # 启动初始化:日志、数据库、缓存等
│   │
│   ├── config/
│   │   ├── mod.rs
│   │   └── settings.rs         # 配置加载:端口、数据库、JWT 等
│   │
│   ├── common/
│   │   ├── mod.rs
│   │   ├── response.rs         # 统一响应结构
│   │   ├── error.rs            # 统一错误处理
│   │   ├── pagination.rs       # 分页参数
│   │   └── result.rs           # AppResult 类型别名
│   │
│   ├── middleware/
│   │   ├── mod.rs
│   │   ├── auth.rs             # 登录校验 / JWT 校验
│   │   ├── logger.rs           # 请求日志
│   │   ├── cors.rs             # 跨域
│   │   └── request_id.rs       # 请求 ID
│   │
│   ├── db/
│   │   ├── mod.rs
│   │   └── pool.rs             # 数据库连接池
│   │
│   ├── modules/
│   │   ├── mod.rs
│   │   │
│   │   ├── user/
│   │   │   ├── mod.rs
│   │   │   ├── routes.rs       # 用户模块路由
│   │   │   ├── handler.rs      # Salvo Handler,接收请求
│   │   │   ├── service.rs      # 业务逻辑
│   │   │   ├── repository.rs   # 数据访问
│   │   │   ├── model.rs        # 数据库模型
│   │   │   └── dto.rs          # 请求/响应 DTO
│   │   │
│   │   ├── auth/
│   │   │   ├── mod.rs
│   │   │   ├── routes.rs
│   │   │   ├── handler.rs
│   │   │   ├── service.rs
│   │   │   └── dto.rs
│   │   │
│   │   └── ticket/
│   │       ├── mod.rs
│   │       ├── routes.rs
│   │       ├── handler.rs
│   │       ├── service.rs
│   │       ├── repository.rs
│   │       ├── model.rs
│   │       └── dto.rs
│   │
│   └── utils/
│       ├── mod.rs
│       ├── jwt.rs
│       ├── password.rs
│       └── time.rs
│
├── tests/
│   ├── user_api_test.rs
│   └── ticket_api_test.rs
└── README.md

二、核心设计思路

1. main.rs 只负责启动

main.rs 不要堆业务代码,只做三件事:加载配置、初始化资源、启动 Salvo 服务。Salvo 官方示例里也是通过 TcpListener 绑定地址,然后 Server::new(...).serve(router).await 启动服务。(GitHub)

use salvo::prelude::*;

mod app;
mod config;
mod common;
mod middleware;
mod db;
mod modules;
mod utils;

#[tokio::main]
async fn main() {
    let app_state = app::bootstrap::init().await;

    let router = app::router::create_router(app_state);

    let acceptor = TcpListener::new("0.0.0.0:7878").bind().await;
    Server::new(acceptor).serve(router).await;
}

2. AppState 放全局共享资源

Salvo 有 Depot 概念,用于一次请求生命周期内的数据传递;如果要共享数据库连接池、配置、缓存等全局状态,可以使用 affix-state 中间件把共享数据注入到 Depot 中。官方文档也明确提到,Affix State 常用于数据库连接池、应用配置、缓存实例、API Client 等共享数据。(Salvo)

use sqlx::PgPool;
use std::sync::Arc;

#[derive(Clone)]
pub struct AppState {
    pub db: PgPool,
    pub jwt_secret: String,
}

pub type SharedState = Arc<AppState>;

3. router.rs 负责总路由装配

Salvo 的路由是树形结构,支持嵌套,也支持在不同层级挂中间件。官方文档也强调 Salvo 的路由系统支持无限嵌套,中间件可以在路由树任意层级使用。(Salvo)

use salvo::prelude::*;
use salvo::affix_state::Inject;

use crate::app::state::SharedState;
use crate::middleware;
use crate::modules;

pub fn create_router(state: SharedState) -> Router {
    Router::new()
        .hoop(Inject::new(state))
        .hoop(middleware::logger::request_logger)
        .push(
            Router::with_path("api")
                .push(modules::auth::routes::routes())
                .push(
                    Router::with_path("admin")
                        .hoop(middleware::auth::jwt_auth)
                        .push(modules::user::routes::routes())
                        .push(modules::ticket::routes::routes())
                )
        )
}

这个结构的好处是:

/api/auth/*             不需要登录
/api/admin/user/*       需要登录
/api/admin/ticket/*     需要登录

认证中间件只挂在 admin 这一层,下面所有子路由自动继承。

三、业务模块结构示例

user 模块为例:

modules/user/
├── mod.rs
├── routes.rs
├── handler.rs
├── service.rs
├── repository.rs
├── model.rs
└── dto.rs

routes.rs

use salvo::prelude::*;

use super::handler;

pub fn routes() -> Router {
    Router::with_path("users")
        .get(handler::list_users)
        .post(handler::create_user)
        .push(
            Router::with_path("<id>")
                .get(handler::get_user)
                .put(handler::update_user)
                .delete(handler::delete_user)
        )
}

handler.rs

Handler 层只负责 HTTP 请求和响应,不要写复杂业务逻辑。Salvo 的 #[handler] 宏可以把普通异步函数直接变成请求处理器,官方文档也说明它可以简化代码,并且可以省略不需要的参数。(Salvo)

use salvo::prelude::*;

use crate::common::response::ApiResponse;
use crate::app::state::SharedState;

use super::dto::CreateUserReq;
use super::service;

#[handler]
pub async fn list_users(depot: &mut Depot, res: &mut Response) {
    let state = depot.obtain::<SharedState>().unwrap();

    let users = service::list_users(&state.db).await.unwrap();

    res.render(Json(ApiResponse::success(users)));
}

#[handler]
pub async fn create_user(
    req: &mut Request,
    depot: &mut Depot,
    res: &mut Response,
) {
    let state = depot.obtain::<SharedState>().unwrap();
    let payload = req.parse_json::<CreateUserReq>().await.unwrap();

    let user = service::create_user(&state.db, payload).await.unwrap();

    res.render(Json(ApiResponse::success(user)));
}

service.rs

Service 层写业务规则,比如校验用户名是否重复、密码加密、权限判断等。

use sqlx::PgPool;

use super::dto::CreateUserReq;
use super::model::User;
use super::repository;

pub async fn list_users(pool: &PgPool) -> anyhow::Result<Vec<User>> {
    let users = repository::find_all(pool).await?;
    Ok(users)
}

pub async fn create_user(pool: &PgPool, req: CreateUserReq) -> anyhow::Result<User> {
    let exists = repository::exists_by_username(pool, &req.username).await?;

    if exists {
        anyhow::bail!("用户名已存在");
    }

    let user = repository::insert(pool, req).await?;
    Ok(user)
}

repository.rs

Repository 层只负责数据库读写。

use sqlx::PgPool;

use super::dto::CreateUserReq;
use super::model::User;

pub async fn find_all(pool: &PgPool) -> anyhow::Result<Vec<User>> {
    let users = sqlx::query_as::<_, User>(
        "SELECT id, username, nickname, created_at FROM users ORDER BY id DESC"
    )
    .fetch_all(pool)
    .await?;

    Ok(users)
}

pub async fn exists_by_username(pool: &PgPool, username: &str) -> anyhow::Result<bool> {
    let count: i64 = sqlx::query_scalar(
        "SELECT COUNT(*) FROM users WHERE username = $1"
    )
    .bind(username)
    .fetch_one(pool)
    .await?;

    Ok(count > 0)
}

pub async fn insert(pool: &PgPool, req: CreateUserReq) -> anyhow::Result<User> {
    let user = sqlx::query_as::<_, User>(
        r#"
        INSERT INTO users(username, nickname)
        VALUES($1, $2)
        RETURNING id, username, nickname, created_at
        "#
    )
    .bind(req.username)
    .bind(req.nickname)
    .fetch_one(pool)
    .await?;

    Ok(user)
}

四、分层职责建议

handler.rs
负责:HTTP 入参、HTTP 出参、调用 service
不要做:复杂业务逻辑、SQL

service.rs
负责:业务规则、流程编排、事务控制
不要做:直接解析 HTTP 请求

repository.rs
负责:数据库 CRUD
不要做:业务判断

dto.rs
负责:请求体、响应体结构

model.rs
负责:数据库实体结构

routes.rs
负责:当前模块路由注册

五、统一响应结构

建议所有接口统一返回:

{
  "code": 0,
  "message": "success",
  "data": {}
}

Rust 结构:

use serde::Serialize;

#[derive(Serialize)]
pub struct ApiResponse<T> {
    pub code: i32,
    pub message: String,
    pub data: Option<T>,
}

impl<T> ApiResponse<T> {
    pub fn success(data: T) -> Self {
        Self {
            code: 0,
            message: "success".to_string(),
            data: Some(data),
        }
    }

    pub fn error(message: impl Into<String>) -> Self {
        Self {
            code: 1,
            message: message.into(),
            data: None,
        }
    }
}

六、中间件设计

Salvo 的一个重要特点是 Middleware = Handler,中间件和接口处理器本质上都是 Handler,这让它的中间件模型比较统一。官方文档也说明,Handler 既可以作为最终 Endpoint,也可以作为中间件处理请求前后的逻辑。(Salvo)

JWT 中间件示例

use salvo::prelude::*;

#[handler]
pub async fn jwt_auth(
    req: &mut Request,
    depot: &mut Depot,
    res: &mut Response,
    ctrl: &mut FlowCtrl,
) {
    let token = req
        .headers()
        .get("Authorization")
        .and_then(|v| v.to_str().ok());

    if token.is_none() {
        res.status_code(StatusCode::UNAUTHORIZED);
        res.render(Json(serde_json::json!({
            "code": 401,
            "message": "未登录或 Token 缺失"
        })));
        return;
    }

    // 这里可以解析 JWT,并把用户信息放入 Depot
    depot.insert("user_id", 1_i64);

    ctrl.call_next(req, depot, res).await;
}

七、Cargo.toml 参考

当前 docs.rs 显示 Salvo latest 为 0.93.0,并且 Salvo 通过 feature flags 控制可选功能,例如 affix-stateoapitower-compatrustlsquinn 等。(Docs.rs)

[package]
name = "salvo-admin-api"
version = "0.1.0"
edition = "2024"

[dependencies]
salvo = { version = "0.93", features = [
    "affix-state",
    "oapi",
    "cors",
    "logging",
] }

tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

serde = { version = "1", features = ["derive"] }
serde_json = "1"

anyhow = "1"
thiserror = "2"

dotenvy = "0.15"
config = "0.15"

sqlx = { version = "0.8", features = [
    "runtime-tokio",
    "postgres",
    "macros",
    "chrono",
    "uuid",
] }

chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["serde", "v4"] }

tracing = "0.1"
tracing-subscriber = "0.3"

jsonwebtoken = "9"
argon2 = "0.5"

具体 feature 名称建议以你实际安装的 Salvo 版本为准,因为不同版本 feature 可能会有变化。

八、适合企业后台的最终推荐架构

如果你是做 IT 工单系统、运维服务中心、后台管理平台,我建议采用下面这种结构:

src/
├── main.rs
├── app/                  # 应用启动、路由装配、状态注入
├── config/               # 配置
├── common/               # 通用响应、错误、分页、常量
├── middleware/           # 登录、权限、日志、跨域、限流
├── db/                   # 数据库连接池、事务封装
├── modules/              # 业务模块
│   ├── auth/             # 登录认证
│   ├── user/             # 用户管理
│   ├── role/             # 角色权限
│   ├── department/       # 部门管理
│   ├── ticket/           # 工单管理
│   ├── approval/         # 审批流
│   ├── asset/            # 资产管理
│   ├── knowledge/        # 知识库
│   └── notification/     # 消息通知
└── utils/                # JWT、密码、时间、文件、加密等工具

九、我的建议结论

对于 Salvo 项目,我建议你按这个模式落地:

入口层:main.rs
应用装配层:app/
公共能力层:common/、middleware/、config/、db/
业务模块层:modules/*
工具层:utils/

每个业务模块内部再统一使用:

routes.rs       路由
handler.rs      HTTP 处理
service.rs      业务逻辑
repository.rs   数据访问
model.rs        数据库模型
dto.rs          请求/响应结构

这样做的好处是:后期你要增加 用户管理、角色权限、工单、审批流、资产、知识库、通知中心 等模块时,不会把代码全部堆在 main.rs 或一个大 handler.rs 里面,项目会比较好维护。