Files
raisfast/docs/guide.md
T

25 KiB
Raw Blame History

raisfast 全栈开发底座 — 产品与技术指导手册

1. 项目概述

基于 Rust + Axum 构建的高性能全栈开发底座。面向个人开发者和小型团队,提供内容管理、插件系统、多租户、RBAC 权限、实时事件等全栈能力,支持 Web 服务器和 Tauri 桌面应用双模式部署。

1.1 项目目标

  • 高性能:利用 Rust 零成本抽象和异步 I/O,单机支撑高并发访问
  • 安全性:密码加盐哈希、JWT 鉴权、XSS/CSRF 防护、SQL 注入防护
  • 易部署:Docker 容器化,单一二进制输出,资源占用低
  • 可扩展:模块化架构,便于后续功能迭代

1.2 目标用户

角色 描述
管理员 系统配置、用户管理、内容审核
作者 创建和发布文章、管理个人内容
读者 浏览文章、评论互动、订阅 RSS

2. 功能规格

2.1 用户系统

2.1.1 注册与登录

  • 邮箱 + 密码注册,邮箱唯一性校验
  • 密码强度校验(最少 8 位,包含字母和数字)
  • 密码使用 Argon2 加盐哈希存储,禁止明文存储
  • 登录成功返回 JWT Access Token15 分钟过期)+ Refresh Token7 天过期)
  • Refresh Token 用于无感刷新 Access Token

2.1.2 用户资料

  • 用户名、头像、个人简介、个人网站链接
  • 头像上传支持 JPEG/PNG,最大 2MB,存储为 WebP 格式

2.1.3 角色与权限

Admin  → 用户管理、内容审核、系统配置、所有 Author 权限
Author → 文章 CRUD、评论管理、标签管理
Reader → 浏览文章、发表评论、管理自己的评论

2.2 文章系统

2.2.1 文章模型

字段 类型 说明
id UUID 主键
title String 标题,最长 200 字符
slug String URL 友好标识,自动从标题生成,唯一
content Text Markdown 格式正文
excerpt String 摘要,最长 500 字符,自动从正文提取
cover_image String 封面图片 URL
status Enum draft / published
author_id UUID 外键关联用户
category_id UUID 外键关联分类
created_at Timestamp 创建时间
updated_at Timestamp 更新时间
published_at Timestamp 发布时间

2.2.2 文章功能

  • Markdown 编辑,服务端渲染为 HTML
  • 草稿自动保存
  • 文章状态流转:draft → published → archived
  • 发布时自动生成 slug(如 my-first-post),支持自定义
  • 文章置顶、排序
  • 浏览量统计(去重,基于 IP + User-Agent

2.2.3 分类与标签

  • 分类:树形结构(支持一级子分类),每篇文章归属一个分类
  • 标签:平铺结构,多对多关系,一篇文章可有多个标签
  • 标签云展示

2.3 评论系统

  • 评论关联文章,支持嵌套回复(最多 3 层)
  • 评论者填写昵称 + 邮箱(未登录用户)或使用已登录身份
  • 管理员可审核/删除评论
  • 支持按时间正序/倒序排列
  • 评论分页加载

2.4 搜索

  • 按关键词搜索文章标题和正文
  • 支持按分类、标签、作者筛选
  • 搜索结果高亮关键词
  • 后续可接入 Meilisearch 提升搜索体验

2.5 媒体管理

  • 图片上传(JPEG、PNG、GIF、WebP
  • 单文件最大 5MB
  • 支持本地存储和 S3 兼容对象存储
  • 图片自动压缩和缩略图生成
  • 上传后返回可访问的 URL

2.6 RSS 订阅

  • 提供 /feed.xml 全局 RSS 订阅
  • 包含最新 20 篇已发布文章
  • 符合 RSS 2.0 规范

2.7 系统管理

  • 站点基础配置(标题、描述、每页文章数)
  • 友情链接管理
  • 导航菜单配置

3. API 设计

3.1 通用约定

  • 基础路径:/api/v1
  • 请求/响应格式:JSON
  • 认证方式:Authorization: Bearer <token>
  • 分页参数:?page=1&page_size=20
  • 排序参数:?sort=created_at&order=desc
  • 时间格式:ISO 86012026-04-10T12:00:00Z

3.2 统一响应结构

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

分页响应:

{
  "code": 0,
  "message": "success",
  "data": {
    "items": [],
    "total": 100,
    "page": 1,
    "page_size": 20
  }
}

错误响应:

{
  "code": 40100,
  "message": "invalid credentials",
  "data": null
}

3.3 错误码规范

范围 说明
0 成功
40000-40099 通用参数错误
40100-40199 认证与授权错误
40400-40499 资源不存在
40900-40999 资源冲突
50000-50099 服务端内部错误

3.4 接口列表

认证

方法 路径 说明 认证
POST /api/v1/auth/register 用户注册
POST /api/v1/auth/login 用户登录
POST /api/v1/auth/refresh 刷新 Token
POST /api/v1/auth/logout 登出

用户

方法 路径 说明 认证
GET /api/v1/users/me 获取当前用户信息
PUT /api/v1/users/me 更新当前用户信息
PUT /api/v1/users/me/password 修改密码
GET /api/v1/users/:id 获取用户公开信息
GET /api/v1/users 获取用户列表(管理员)

文章

方法 路径 说明 认证
GET /api/v1/posts 文章列表
GET /api/v1/posts/:slug 文章详情
POST /api/v1/posts 创建文章
PUT /api/v1/posts/:slug 更新文章
DELETE /api/v1/posts/:slug 删除文章

分类

方法 路径 说明 认证
GET /api/v1/categories 分类列表
POST /api/v1/categories 创建分类
PUT /api/v1/categories/:id 更新分类
DELETE /api/v1/categories/:id 删除分类

标签

方法 路径 说明 认证
GET /api/v1/tags 标签列表
POST /api/v1/tags 创建标签
DELETE /api/v1/tags/:id 删除标签

评论

方法 路径 说明 认证
GET /api/v1/posts/:slug/comments 文章评论列表
POST /api/v1/posts/:slug/comments 发表评论 可选
DELETE /api/v1/comments/:id 删除评论

媒体

方法 路径 说明 认证
POST /api/v1/media/upload 上传文件
GET /api/v1/media 文件列表
DELETE /api/v1/media/:id 删除文件

RSS

方法 路径 说明 认证
GET /feed.xml RSS 订阅

4. 数据库设计

4.1 ER 关系概览

users ──1:N── posts ──1:N── comments
  │            │
  │            ├── N:1 ── categories
  │            │
  │            └── M:N ── posts_tags ── M:N ── tags
  │
  └──1:N── media

4.2 表结构

users

列名 类型 约束 说明
id UUID PK 主键
email VARCHAR(255) UNIQUE, NOT NULL 邮箱
username VARCHAR(50) UNIQUE, NOT NULL 用户名
password_hash VARCHAR(255) NOT NULL Argon2 哈希
role VARCHAR(20) NOT NULL, DEFAULT 'reader' 角色
avatar VARCHAR(500) 头像 URL
bio TEXT 个人简介
website VARCHAR(500) 个人网站
created_at TEXT NOT NULL 创建时间(ISO 8601
updated_at TEXT NOT NULL 更新时间(ISO 8601

posts

列名 类型 约束 说明
id UUID PK 主键
title VARCHAR(200) NOT NULL 标题
slug VARCHAR(250) UNIQUE, NOT NULL URL 标识
content TEXT NOT NULL Markdown 正文
excerpt VARCHAR(500) 摘要
cover_image VARCHAR(500) 封面图 URL
status VARCHAR(20) NOT NULL, DEFAULT 'draft' draft/published/archived
author_id UUID FK → users.id 作者
category_id UUID FK → categories.id 分类
view_count INTEGER NOT NULL, DEFAULT 0 浏览量
is_pinned BOOLEAN NOT NULL, DEFAULT 0 是否置顶
created_at TEXT NOT NULL 创建时间(ISO 8601
updated_at TEXT NOT NULL 更新时间(ISO 8601
published_at TEXT 发布时间(ISO 8601

categories

列名 类型 约束 说明
id UUID PK 主键
name VARCHAR(100) UNIQUE, NOT NULL 分类名
slug VARCHAR(120) UNIQUE, NOT NULL URL 标识
description VARCHAR(500) 描述
parent_id UUID FK → categories.id 父分类
sort_order INTEGER NOT NULL, DEFAULT 0 排序
created_at TEXT NOT NULL 创建时间(ISO 8601

tags

列名 类型 约束 说明
id UUID PK 主键
name VARCHAR(50) UNIQUE, NOT NULL 标签名
slug VARCHAR(60) UNIQUE, NOT NULL URL 标识
created_at TEXT NOT NULL 创建时间(ISO 8601

posts_tags

列名 类型 约束 说明
post_id UUID FK → posts.id 文章 ID
tag_id UUID FK → tags.id 标签 ID

主键:(post_id, tag_id)

comments

列名 类型 约束 说明
id UUID PK 主键
post_id UUID FK → posts.id 所属文章
author_id UUID FK → users.id, NULLABLE 登录用户
nickname VARCHAR(50) 游客昵称
email VARCHAR(255) 游客邮箱
content TEXT NOT NULL 评论内容
parent_id UUID FK → comments.id, NULLABLE 父评论
status VARCHAR(20) NOT NULL, DEFAULT 'pending' pending/approved/spam
created_at TEXT NOT NULL 创建时间(ISO 8601

media

列名 类型 约束 说明
id UUID PK 主键
user_id UUID FK → users.id 上传者
filename VARCHAR(255) NOT NULL 原始文件名
filepath VARCHAR(500) NOT NULL 存储路径
mimetype VARCHAR(100) NOT NULL MIME 类型
size INTEGER NOT NULL 文件大小(字节)
created_at TEXT NOT NULL 创建时间(ISO 8601

refresh_tokens

列名 类型 约束 说明
id UUID PK 主键
user_id UUID FK → users.id 所属用户
token VARCHAR(255) UNIQUE, NOT NULL Refresh Token
expires_at TEXT NOT NULL 过期时间(ISO 8601
created_at TEXT NOT NULL 创建时间(ISO 8601

5. 技术架构

5.1 技术栈

核心框架

层级 技术 版本 用途
语言 Rust Edition 2024 核心开发语言
Web 框架 axum 0.8.x HTTP 路由、中间件、提取器
异步运行时 tokio 1.x (full features) 异步 I/O 运行时
中间件 tower / tower-http 0.5.x / 0.6.x CORS、限流、请求日志、压缩、静态文件

数据层

层级 技术 版本 用途
数据库 SQLite 3.x 轻量级嵌入式数据存储(开发阶段)
SQL 工具 sqlx 0.8.x 编译期 SQL 检查、异步查询
迁移 sqlx-cli 0.8.x 数据库版本管理

序列化与数据

层级 技术 版本 用途
序列化 serde / serde_json 1.x JSON 序列化/反序列化
UUID uuid 1.x (v7) 主键生成,时间排序友好
时间 chrono 0.4.x 时间处理,序列化配合 serde

安全与认证

层级 技术 版本 用途
密码哈希 argon2 0.5.x Argon2id 密码安全哈希(OWASP 推荐)
JWT jsonwebtoken 9.x Token 签发与验证
HTML 净化 ammonia 0.16.x XSS 防护,清理用户提交的 HTML
CSRF tokio (nonce) CSRF Token 生成与校验

业务功能

层级 技术 版本 用途
Markdown comrak 0.36.x CommonMark/GFM 兼容 Markdown → HTML 渲染
代码高亮 syntect 5.x Markdown 内代码块语法高亮
Slug 生成 slug 0.1.x URL 友好标题生成
校验 validator 0.19.x 声明式请求参数校验(derive 宏)
图片处理 image 0.25.x 图片压缩、缩略图生成、格式转换
RSS rss 2.x RSS 2.0 订阅源生成

错误处理与日志

层级 技术 版本 用途
应用错误 anyhow 1.x 应用级通用错误传播(Service 层内部)
业务错误 thiserror 2.x 自定义错误类型 derive(定义 AppError
日志 tracing 0.1.x 结构化日志,异步友好,支持 span
日志格式 tracing-subscriber 0.3.x 日志输出格式化和过滤

配置与环境

层级 技术 版本 用途
环境变量 dotenvy 0.15.x .env 文件加载
配置管理 config 0.14.x 多格式配置文件 + 环境变量 + 类型安全

测试与质量

层级 技术 版本 用途
单元测试 #[test] + rstest 0.23.x 参数化测试、fixture 注入
快照测试 insta 1.x API 响应快照测试,防回归
HTTP 集成测试 axum::test / reqwest 0.12.x Handler 级别集成测试
Mock mockall 0.13.x Service 层 Mock,隔离数据库依赖
基准测试 criterion 0.5.x 性能基准测试,防性能退化

开发工具链(CI 必装)

工具 用途
cargo fmt 代码格式化,统一风格
cargo clippy 代码 Lint,零警告要求
cargo audit 安全漏洞审计(检查依赖 CVE
cargo deny 许可证合规 + 安全策略检查
cargo outdated 依赖版本检查
cargo nextest 更快的测试运行器(比 cargo test 快 3x+
cargo insta test --review 快照测试审查流程

5.2 项目结构

hello-axum/
├── Cargo.toml
├── .env                        # 环境变量(不入库)
├── .env.example                # 环境变量示例
├── migrations/                 # 数据库迁移文件
│   └── 001_init.sql
├── docs/                       # 项目文档
│   └── guide.md
├── static/                     # 静态资源
├── uploads/                    # 上传文件目录
└── src/
    ├── main.rs                 # 入口:启动服务器
    ├── config/
    │   ├── mod.rs
    │   └── app.rs              # 应用配置(从环境变量加载)
    ├── db/
    │   ├── mod.rs
    │   └── connection.rs       # 数据库连接池初始化
    ├── models/
    │   ├── mod.rs
    │   ├── user.rs
    │   ├── post.rs
    │   ├── category.rs
    │   ├── tag.rs
    │   ├── comment.rs
    │   └── media.rs
    ├── handlers/
    │   ├── mod.rs
    │   ├── auth.rs             # 注册、登录、刷新、登出
    │   ├── user.rs             # 用户信息
    │   ├── post.rs             # 文章 CRUD
    │   ├── category.rs         # 分类管理
    │   ├── tag.rs              # 标签管理
    │   ├── comment.rs          # 评论
    │   ├── media.rs            # 文件上传
    │   └── rss.rs              # RSS 订阅
    ├── services/
    │   ├── mod.rs
    │   ├── auth.rs             # 认证业务逻辑
    │   ├── post.rs             # 文章业务逻辑
    │   ├── comment.rs          # 评论业务逻辑
    │   └── media.rs            # 媒体业务逻辑
    ├── middleware/
    │   ├── mod.rs
    │   ├── auth.rs             # JWT 认证中间件
    │   └── rate_limit.rs       # 限流中间件
    ├── errors/
    │   ├── mod.rs
    │   └── app_error.rs        # 统一错误类型(实现 IntoResponse
    └── utils/
        ├── mod.rs
        ├── pagination.rs       # 分页提取器
        └── slug.rs             # Slug 生成工具

5.3 架构分层

┌──────────────────────────────────┐
│           HTTP 请求              │
└──────────────┬───────────────────┘
               ▼
┌──────────────────────────────────┐
│        路由层 (Router)           │  axum 路由定义、中间件挂载
└──────────────┬───────────────────┘
               ▼
┌──────────────────────────────────┐
│       处理层 (Handler)           │  参数提取、校验、调用 Service
└──────────────┬───────────────────┘
               ▼
┌──────────────────────────────────┐
│       业务层 (Service)           │  核心业务逻辑、事务管理
└──────────────┬───────────────────┘
               ▼
┌──────────────────────────────────┐
│       数据层 (Model / DB)        │  SQL 查询、数据映射
└──────────────────────────────────┘
  • Handler 只负责提取请求参数、调用 Service、构造响应,不包含业务逻辑
  • Service 包含所有业务逻辑,通过参数接收数据库连接,便于测试
  • Model 定义数据结构和数据库操作,与数据库表一一对应
  • Middleware 处理横切关注点(认证、日志、限流)

6. 安全设计

6.1 认证与授权

  • JWT 签发使用 HS256 或 RS256 算法
  • Access Token 短过期(15 分钟),Refresh Token 长过期(7 天)
  • Refresh Token 存储在数据库中,支持吊销
  • 密码使用 Argon2id 算法哈希,自动加盐

6.2 输入防护

  • 所有用户输入通过 validator 进行服务端校验
  • Markdown 渲染时过滤危险的 HTML 标签(XSS 防护)
  • 使用 sqlx 参数化查询,杜绝 SQL 注入
  • 请求体大小限制(默认 2MB

6.3 传输安全

  • 生产环境强制 HTTPS
  • Cookie 设置 HttpOnlySecureSameSite=Strict
  • CORS 白名单配置

6.4 限流

  • 登录接口:同一 IP 每分钟最多 10 次
  • 注册接口:同一 IP 每小时最多 5 次
  • 评论接口:同一用户每分钟最多 3 次
  • 通用接口:同一 IP 每分钟最多 60 次

7. 开发阶段规划

Phase 1:项目骨架(1-2 天)

  • 初始化项目结构,配置 Cargo.toml 依赖
  • 搭建 axum 基础路由和服务器
  • 配置 SQLite 连接池
  • 实现统一错误处理
  • 实现统一响应格式
  • 配置日志和 .env 管理
  • 编写初始数据库迁移

Phase 2:用户认证(2-3 天)

  • 用户注册(参数校验、密码哈希)
  • 用户登录(JWT 签发)
  • JWT 认证中间件
  • Token 刷新机制
  • 用户信息查询和更新

Phase 3:文章核心(3-4 天)

  • 文章 CRUD
  • 分类 CRUD
  • 标签 CRUD 及多对多关联
  • Markdown 渲染
  • Slug 自动生成
  • 分页和排序

Phase 4:评论与互动(2 天)

  • 评论发表和列表
  • 嵌套评论
  • 评论审核

Phase 5:媒体与辅助功能(2-3 天)

  • 图片上传(本地存储)
  • RSS 订阅
  • 全文搜索(基础版)
  • 浏览量统计

Phase 6:安全与优化(1-2 天)

  • Rate Limiting 中间件
  • CORS 配置
  • 输入校验完善
  • 性能优化(数据库索引、查询优化)

Phase 7:部署(1 天)

  • Dockerfile 和 docker-compose
  • Nginx 反向代理配置
  • CI/CD 流程
  • 生产环境配置

8. 环境变量

# 应用配置
APP_HOST=0.0.0.0
APP_PORT=3000
APP_ENV=development

# 数据库
DATABASE_URL=sqlite:./data/raisfast.db?mode=rwc

# JWT
JWT_SECRET=your-secret-key-at-least-32-characters
JWT_ACCESS_EXPIRES=900          # 15 分钟,单位秒
JWT_REFRESH_EXPIRES=604800      # 7 天,单位秒

# 媒体上传
UPLOAD_DIR=./uploads
MAX_UPLOAD_SIZE=5242880         # 5MB,单位字节

# 日志
RUST_LOG=hello_axum=debug,tower_http=debug

9. 编码规范

9.1 Rust 风格

  • 遵循 rustfmt 默认配置,使用 cargo fmt 格式化
  • 使用 cargo clippy 检查代码质量,零警告
  • 公开函数和类型必须添加文档注释 ///
  • 禁止使用 unsafe,除非在极端性能热点且经过团队评审
  • 使用 #![deny(unsafe_code)] 在 crate 级别禁止 unsafe
  • 充分利用 Rust 类型系统:用 Option<T> 表示可能缺失,用 Result<T, E> 表示可能失败,用枚举表示有限状态
  • 优先使用 impl Into<String> / AsRef<str> 而非固定 String 参数,减少不必要的分配
  • 使用 Cow<str> 处理可能借用也可能拥有的字符串
  • 优先使用迭代器方法链(.map() / .filter() / .collect())替代命令式循环
  • 使用 #[non_exhaustive] 标记公开枚举和结构体,保证 API 向后兼容

9.2 命名约定

  • 文件名:snake_case(如 auth_handler.rs
  • 类型/结构体:PascalCase(如 CreatePostRequest
  • 函数/变量:snake_case(如 create_post
  • 常量:SCREAMING_SNAKE_CASE(如 MAX_PAGE_SIZE
  • 数据库列:snake_case
  • API 路径:kebab-case(如 /api/v1/refresh-tokens

9.3 错误处理

  • 错误定义:使用 thiserror derive 宏定义 AppError 枚举,每个变体对应一类业务错误
  • 错误传播Service 内部使用 anyhow::Result 简化错误传播,在 Handler 边界转换为 AppError
  • AppError 实现 IntoResponse,自动转换为 HTTP 响应
  • 禁止使用 unwrap()expect() 在非测试代码中,使用 ? 或显式错误处理
  • 数据库错误映射为业务语义错误(如唯一约束冲突 → 409 Conflict
  • 使用 .ok_or(AppError::...)? 将 Option 转为 Result
  • 使用 thiserror#[source] 属性保留错误链,配合 tracing 输出完整上下文
#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("resource not found: {0}")]
    NotFound(String),

    #[error("unauthorized")]
    Unauthorized,

    #[error("forbidden")]
    Forbidden,

    #[error("bad request: {0}")]
    BadRequest(String),

    #[error("conflict: {0}")]
    Conflict(String),

    #[error("internal server error")]
    Internal(#[from] anyhow::Error),
}

9.4 测试要求

  • 每个 Service 函数编写单元测试
  • 每个 Handler 编写集成测试
  • 使用测试数据库,测试后清理
  • 使用 rstest 编写参数化测试,减少重复
  • 使用 insta 做 API 响应快照测试
  • 使用 mockall Mock Service 层,隔离数据库依赖
  • 目标测试覆盖率 > 70%
  • CI 中使用 cargo nextest run 加速测试执行

9.5 性能要求

  • 数据库连接使用连接池(sqlx 内置),避免频繁建连
  • 热点查询使用 sqlx::query_scalar! 等编译期检查宏,减少运行时开销
  • 分页查询必须使用 LIMIT + OFFSET 或游标分页,禁止全表扫描
  • 使用 Arc<T> 共享状态,避免深层克隆(如 Arc<Pool>
  • 字符串处理优先使用 &str 借用,仅必要时转为 String
  • 大量数据使用流式处理(axum::body::BodyStream),避免全量加载到内存
  • 静态资源使用 tower-http::services::ServeDir,配合 CompressionLayer 压缩

9.6 Rust Idiom 清单

以下为必须遵循的 Rust 惯用法:

场景 推荐做法 反模式
错误处理 Result<T, E> + ? unwrap() / expect() / panic!
空值 Option<T> null / 占位默认值
字符串参数 &str / AsRef<str> 到处 String 克隆
共享所有权 Arc<T> 大量 clone()
配置初始化 std::sync::LazyLock / once_cell 全局 static mut
并发安全 Mutex<T> / RwLock<T> / tokio::sync 手动加锁 / unsafe
类型转换 From<T> / Into<T> / TryFrom<T> 手动 as 强转
条件编译 #[cfg(test)] / features 运行时判断
资源管理 RAIIDrop 守卫) 手动 close() / free()

10. 部署方案

10.1 Docker

FROM rust:1.85 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
COPY --from=builder /app/target/release/hello-axum /usr/local/bin/
COPY static/ /app/static/
EXPOSE 3000
CMD ["hello-axum"]

10.2 docker-compose

services:
  app:
    build: .
    ports:
      - "3000:3000"
    env_file: .env
    volumes:
      - raisfast-data:/app/data
      - uploads:/app/uploads

  db:
    image: postgres:16
    profiles:
      - "postgres"
    environment:
      POSTGRES_DB: raisfast
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  raisfast-data:
  pgdata:
  uploads:

10.3 Nginx 反向代理

server {
    listen 80;
    server_name raisfast.example.com;

    client_max_body_size 5M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /uploads/ {
        alias /app/uploads/;
        expires 30d;
    }
}