我的 Rust 后端之旅:从0到1的实践指南


📦 GitHub:https://github.com/idaibin/rustzen-admin

一、Rustzen 是什么?为什么这样命名?

Rustzen 是我为这个项目取的名字,寓意 “Rust + Zen”: Rust 带来性能与安全,Zen 代表极简与秩序。

这不仅是一种技术栈的组合,更是我对开发体验的思考方式:

极简不是“少做”,而是“恰到好处”。Rustzen 的每一次演进,都是对“合适的设计+清晰的边界”的追问。

我是从 Tauri + SQLite 起步,逐步转向 Axum + SQLx,在不断演化中逐步打磨出当前这套结构。 而这套结构的出发点,正是面对如下问题的回应:

Rustzen 的出现,是对这些问题的回应——不是追求复杂的框架,而是寻找“结构上的刚刚好”。


二、为什么采用三层架构?

为什么这样设计?

结构清晰是可维护性的基础,三层分离让每一层只关注自己的职责,降低认知负担。

层级职责说明核心特点
router路由定义、权限绑定、HTTP 处理,功能入口直观Router + Handler 已合并
service聚合业务逻辑,聚焦行为、封装处理流程纯业务逻辑,无框架依赖
repo数据库访问,封装 SQL 查询第一现场错误处理

目录示例:

features/
└── system/
    ├── mod.rs           // 模块声明
    ├── user/
    │   ├── mod.rs
    │   ├── router.rs    // 路由 + 处理器
    │   ├── service.rs   // 业务逻辑
    │   ├── repo.rs      // 数据访问
    │   ├── entity.rs    // 数据库实体
    │   ├── dto.rs       // 请求数据传输对象
    │   └── vo.rs        // 响应视图对象
    ├── role/
    └── menu/

三、数据模型为何拆分 entity/dto/vo?

为什么这样设计?

entity/dto/vo 分离,避免字段冗余和数据泄漏,便于演进和协作。

模块说明示例
entity映射数据库结构,用于 SQLx 查询UserWithRolesEntity
dto接收前端入参,字段校验CreateUserDto, UserQueryDto
vo返回前端展示所需字段,安全脱敏UserListVo, UserDetailVo

四、认证与授权系统:基于 JWT + 服务端权限缓存

系统概览:

4.1 中间件架构

核心中间件组件

中间件实现示例

// JWT认证中间件 - 只负责身份认证
pub async fn jwt_auth_middleware(
    mut request: Request,
    next: Next,
) -> Result<Response, AppError> {
    let token = extract_token_from_header(&request)?;
    // JWT只包含用户身份信息
    let user_claims = verify_jwt_claims(&token)?;
    request.extensions_mut().insert(CurrentUser { id: user_claims.user_id });
    Ok(next.run(request).await)
}

// 权限中间件 - 服务端查缓存
pub async fn permission_middleware(
    request: Request,
    next: Next,
    permissions: PermissionsCheck,
) -> Result<Response, AppError> {
    let current_user = request.extensions().get::<CurrentUser>()
        .ok_or(ServiceError::InvalidToken)?;
    // 通过用户ID查找服务端缓存的权限
    let has_permission = PermissionService::check_permissions(current_user.id, &permissions).await?;
    if !has_permission {
        return Err(ServiceError::PermissionDenied.into());
    }
    Ok(next.run(request).await)
}

声明式权限使用示例

.route_with_permission(
    "/",
    get(get_user_list),
    PermissionsCheck::Any(vec!["system:*", "system:user:*", "system:user:list"]),
)

4.2 权限缓存和更新机制

为什么这样设计?

权限缓存减轻了数据库压力,更新机制确保了活跃的用户体验,过期自动清理增强了安全性。

如何工作:

权限缓存实现示例

// 权限缓存管理器
pub struct PermissionCacheManager {
    cache: Arc<RwLock<HashMap<i64, UserPermissionCache>>>,
}

impl PermissionService {
    pub async fn check_permissions(
        user_id: i64,
        permissions_check: &PermissionsCheck,
    ) -> Result<bool, ServiceError> {
        // 检查缓存是否存在
        if let Some(cache) = PERMISSION_CACHE.get(user_id) {
            // 检查缓存是否过期
            if cache.is_expired() {
                PERMISSION_CACHE.remove(user_id);
                return Err(ServiceError::InvalidToken);
            }
            // 检查权限
            let has_permission = permissions_check.check(&cache.permissions);
            return Ok(has_permission);
        }
        Err(ServiceError::InvalidToken)
    }
}

// 权限缓存续期 - 在 AuthService 中实现
impl AuthService {
    // 获取用户登录信息
    #[tracing::instrument(name = "get_login_info", skip(pool))]
    pub async fn get_login_info(
        pool: &PgPool,
        user_id: i64,
    ) -> Result<UserInfoResponse, ServiceError> {
        // ...省略...
        // 刷新用户权限缓存
        Self::refresh_user_permissions_cache(user_id, user.is_super_admin, permissions).await?;
        Ok(user_info)
    }

    pub async fn refresh_user_permissions_cache(
        user_id: i64,
        is_super_admin: bool,
        permissions: Vec<String>,
    ) -> Result<(), ServiceError> {
        if is_super_admin {
            // 超级管理员权限缓存
            PermissionService::cache_user_permissions(user_id, vec!["*".to_string()]);
            return Ok(());
        }
        // 缓存用户权限
        PermissionService::cache_user_permissions(user_id, permissions.clone());
        Ok(())
    }
}

4.3 权限粒度与声明


4.4 性能优化策略


五、错误处理与统一响应

为什么这样设计?

统一的错误类型和响应结构,提升前后端协作效率,便于 AI 自动生成接口文档和测试用例。

#[derive(Debug, thiserror::Error)]
pub enum ServiceError {
    #[error("用户被禁用")]
    UserIsDisabled,
    #[error("用户名已存在")]
    UsernameConflict,
    #[error("数据库查询失败")]
    DatabaseQueryFailed,
    #[error("{0} 未找到")]
    NotFound(String),
    #[error("权限不足")]
    PermissionDenied,
    #[error("无效或过期的令牌")]
    InvalidToken,
    // ... 更多业务错误类型
}

错误转换机制

impl From<ServiceError> for AppError {
    fn from(err: ServiceError) -> Self {
        let (status, code, message) = match err {
            ServiceError::NotFound(resource) => (
                StatusCode::NOT_FOUND,
                10001,
                format!("{} 未找到", resource),
            ),
            ServiceError::UsernameConflict => (
                StatusCode::CONFLICT,
                10201,
                "用户名已存在".to_string(),
            ),
            ServiceError::DatabaseQueryFailed => (
                StatusCode::INTERNAL_SERVER_ERROR,
                20001,
                "服务暂时不可用,请稍后重试".to_string(),
            ),
            ServiceError::InvalidToken => (
                StatusCode::UNAUTHORIZED,
                30000,
                "无效或过期的令牌,请重新登录".to_string(),
            ),
            // ... 完整的错误映射
        };
        AppError((status, code, message))
    }
}

六、响应格式:两种方案统一处理

方案一:简单响应格式

// 单个对象或非分页响应
ApiResponse::success(data)

// 响应JSON:
{
  "code": 0,
  "message": "Success",
  "data": {
    "id": 1,
    "username": "admin"
  }
}

方案二:标准分页格式

// 分页列表接口
ApiResponse::page(data, total)

// 响应JSON:
{
  "code": 0,
  "message": "Success",
  "data": [...],
  "total": 100
}
// 在 router.rs 中
pub async fn get_user_list(...) -> AppResult<Vec<UserListVo>> {
    let (users, total) = UserService::get_user_list(&pool, query).await?;
    Ok(ApiResponse::page(users, total))  // 分页格式
}

pub async fn get_user_by_id(...) -> AppResult<UserDetailVo> {
    let user = UserService::get_user_by_id(&pool, id).await?;
    Ok(ApiResponse::success(user))  // 简单格式
}

七、数据库迁移策略:Zen 风格编号体系

类型示例编号描述
表结构100100 ~ 100500用户、角色、菜单等基础表
外键 / 触发器100800, 100900表间约束、日志归档
视图1070xx聚合视图,如用户权限
函数1080xx查询封装、登录、权限计算
数据种子1090xx初始化用户、菜单、字典数据等

八、模块间依赖关系管理

清晰的模块边界

// 在 system/mod.rs 中
pub fn system_routes() -> Router<PgPool> {
    Router::new()
        .nest("/users", user_routes())
        .nest("/menus", menu_routes())
        .nest("/roles", role_routes())
        .nest("/dicts", dict_routes())
        .nest("/logs", log_routes())
}

面对复杂,我们选择清晰; 面对冗余,我们选择必要; 面对混乱,我们选择 Zen。

Rustzen 不是一套框架,而是一种思考方式。 希望它不仅写给现在的我,也能写给未来那些,也在路上的你。

九、AI 辅助编程:结构越 Zen,效率越高

十、结语:Rust + Zen,极简不是少,而是恰好

Rustzen 是一次从混沌到秩序的架构探索。

极简不是”少做”,而是简洁设计、恰到好处;它为 AI 协作、团队合作、未来扩展都提供了一个清晰的基础。

架构优势总结


未来改进方向


十、结尾寄语:成长中的项目,欢迎共鸣

Rustzen 是我从前端转向后端探索的实践结晶,它不是一个终点,而是一条通往秩序与清晰的路。

愿每一位开发者在追求“恰到好处”的过程中,找到属于自己的平衡与方向。

欢迎反馈和共建,让 Rustzen 成为更多开发者的“恰到好处”之选!


🔗 项目地址与更多内容