mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-24 08:02:36 +00:00
824 lines
25 KiB
Markdown
824 lines
25 KiB
Markdown
# 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 Token(15 分钟过期)+ Refresh Token(7 天过期)
|
||
- 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 8601(`2026-04-10T12:00:00Z`)
|
||
|
||
### 3.2 统一响应结构
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": { }
|
||
}
|
||
```
|
||
|
||
分页响应:
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"items": [],
|
||
"total": 100,
|
||
"page": 1,
|
||
"page_size": 20
|
||
}
|
||
}
|
||
```
|
||
|
||
错误响应:
|
||
|
||
```json
|
||
{
|
||
"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 设置 `HttpOnly`、`Secure`、`SameSite=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. 环境变量
|
||
|
||
```env
|
||
# 应用配置
|
||
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` 输出完整上下文
|
||
|
||
```rust
|
||
#[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 | 运行时判断 |
|
||
| 资源管理 | RAII(Drop 守卫) | 手动 `close()` / `free()` |
|
||
|
||
---
|
||
|
||
## 10. 部署方案
|
||
|
||
### 10.1 Docker
|
||
|
||
```dockerfile
|
||
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
|
||
|
||
```yaml
|
||
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 反向代理
|
||
|
||
```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;
|
||
}
|
||
}
|
||
```
|