18 KiB
博客系统 — 详细实现计划
验收方式:每一步完成后,执行
cargo fmt --check && cargo clippy -- -D warnings && cargo build && cargo test全部通过,即可标记[x]。文档约定:
[ ]未完成,[x]已完成。
Phase 1:项目骨架 ✅
1.1 配置 Cargo.toml 依赖
- 在
[dependencies]中添加以下依赖:axum、tokio(features = full)、tower、tower-http(features = cors, trace, limit)sqlx(features = runtime-tokio, sqlite)、serde、serde_jsonuuid(features = v7, serde)、chrono(features = serde)argon2、jsonwebtokenthiserror、anyhowtracing、tracing-subscriber(features = env-filter)dotenvy、validator(features = derive)comrak、slug、ammoniaimage、rss
- 在
src/main.rs顶部添加#![deny(unsafe_code)] cargo build编译通过
验收:cargo build 无报错
1.2 创建目录结构与模块声明
- 创建以下目录和文件:
src/ ├── main.rs ├── lib.rs ├── config/ │ └── mod.rs ├── db/ │ └── mod.rs ├── models/ │ └── mod.rs ├── handlers/ │ └── mod.rs ├── services/ │ └── mod.rs ├── middleware/ │ └── mod.rs ├── errors/ │ └── mod.rs └── utils/ └── mod.rs - 每个模块文件包含基本内容(可为空
pub mod xxx;或占位) src/lib.rs声明所有子模块:pub mod config; pub mod db; ...cargo build编译通过
验收:cargo build 无报错,目录结构完整
1.3 环境变量与配置加载
- 创建
.env.example,包含APP_HOST、APP_PORT、APP_ENV、DATABASE_URL、JWT_SECRET、JWT_ACCESS_EXPIRES、JWT_REFRESH_EXPIRES、UPLOAD_DIR、MAX_UPLOAD_SIZE、RUST_LOG - 创建
.env(从.env.example复制,填入开发用默认值) - 将
.env加入.gitignore - 在
src/config/mod.rs中定义AppConfig结构体,从环境变量加载:pub struct AppConfig { pub host: String, pub port: u16, pub env: String, pub database_url: String, pub jwt_secret: String, pub jwt_access_expires: u64, pub jwt_refresh_expires: u64, pub upload_dir: String, pub max_upload_size: usize, } - 实现
AppConfig::from_env()方法,使用dotenvy::dotenv()加载.env cargo build编译通过
验收:单元测试验证 AppConfig::from_env() 正确读取 .env 中的值
1.4 SQLite 连接池初始化
- 在
src/db/mod.rs中实现init_pool(database_url: &str) -> SqlitePool - 使用
SqlitePoolOptions配置连接池(max_connections = 5) - 启用 SQLite
PRAGMA journal_mode = WAL、PRAGMA foreign_keys = ON - 在
src/main.rs中调用init_pool并将SqlitePool放入 axum State cargo build编译通过
验收:运行程序不报数据库连接错误;可在测试中执行简单查询 SELECT 1
1.5 统一错误处理 AppError
- 在
src/errors/mod.rs中定义AppError枚举:#[derive(Debug, thiserror::Error)] pub enum AppError { #[error("bad request: {0}")] BadRequest(String), // 400 #[error("unauthorized")] Unauthorized, // 401 #[error("forbidden")] Forbidden, // 403 #[error("not found: {0}")] NotFound(String), // 404 #[error("conflict: {0}")] Conflict(String), // 409 #[error("internal server error")] Internal(#[from] anyhow::Error), // 500 } - 为
AppError实现IntoResponse,返回 JSON 格式{ "code": xxx, "message": "xxx", "data": null } - 定义
AppResult<T> = Result<T, AppError>类型别名 - 为 sqlx::Error 实现到 AppError 的转换(唯一约束冲突 → Conflict)
cargo build编译通过
验收:Handler 返回 AppError::NotFound("post".into()) 时 HTTP 响应 404 + 正确 JSON body
1.6 统一响应格式 ApiResponse
- 在
src/errors/mod.rs(或独立src/utils/response.rs)中定义:#[derive(Serialize)] pub struct ApiResponse<T: Serialize> { pub code: i32, pub message: String, pub data: Option<T>, } - 实现
ApiResponse::success(data)和ApiResponse::error(code, message)构造方法 - 定义分页结构
PaginatedData<T>:pub struct PaginatedData<T> { pub items: Vec<T>, pub total: i64, pub page: i64, pub page_size: i64, } cargo build编译通过
验收:ApiResponse::success(Some("hello")) 序列化为 {"code":0,"message":"success","data":"hello"}
1.7 tracing 日志初始化
- 在
src/main.rs中初始化 tracing:tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .init(); - 在关键位置添加
tracing::info!日志(服务器启动、数据库连接等) cargo build编译通过
验收:设置 RUST_LOG=hello_axum=debug 运行程序,终端可见结构化日志输出
1.8 axum 路由与服务器启动 + 健康检查
- 在
src/handlers/mod.rs实现健康检查 handler:GET /api/v1/health → { "code": 0, "message": "success", "data": { "status": "ok" } } - 在
src/main.rs中组装 axum Router:- 挂载
/api/v1/health路由 - 使用
tower::ServiceBuilder添加TraceLayer(请求日志) - 使用
axum::extract::State共享SqlitePool和AppConfig
- 挂载
- 使用
tokio::net::TcpListener绑定地址,axum::serve启动 - 添加 graceful shutdown(监听 ctrl+c)
cargo build编译通过
验收:cargo run 启动后 curl http://localhost:3000/api/v1/health 返回 200 + JSON
1.9 初始数据库迁移
- 安装
cargo install sqlx-cli --no-default-features --features sqlite - 创建
migrations/目录 - 编写
migrations/001_init.sql,包含所有表:userscategoriestagspostsposts_tagscommentsmediarefresh_tokens
- 每张表的字段类型、约束、外键、索引与
docs/guide.md第 4 节一致 - 为
posts添加索引:slug、status、author_id、category_id、created_at - 为
comments添加索引:post_id、status - 为
users添加索引:email、username - 运行迁移成功执行
- 验证表结构:
sqlite3 data/blog.db ".tables"和.schema users
验收:sqlite3 data/blog.db ".tables" 输出 8 张表,schema 与文档一致
1.10 Phase 1 最终验证
cargo fmt --check通过(无格式问题)cargo clippy -- -D warnings通过(零警告)cargo build通过cargo test通过cargo run启动后健康检查返回 200- 数据库迁移正常
Phase 2:用户认证 ✅
2.1 用户数据模型
- 在
src/models/user.rs中定义数据库模型 - 实现查询函数:
find_by_email、find_by_id、create、update_profile - 在
src/models/mod.rs中声明pub mod user; cargo build通过
验收:模型和查询函数编译通过
2.2 用户注册
- 定义请求结构体
RegisterRequest(email, username, password)并使用validatorderive 校验 - 定义响应结构体
UserResponse(不含 password_hash) - 在
src/services/auth.rs实现register(校验唯一性、argon2 哈希、UUID v7) - 在
src/handlers/auth.rs实现POST /api/v1/auth/registerhandler - 在 Router 中注册路由
cargo build通过
验收:
- 注册返回 200 + 用户信息(无密码)
- 重复注册返回 409
2.3 用户登录与 JWT 签发
- 定义
LoginRequest和LoginResponse - 实现 JWT 工具函数(generate_access_token, verify_token)
- 在
src/services/auth.rs实现login - 在
src/handlers/auth.rs实现POST /api/v1/auth/loginhandler cargo build通过
验收:
- 正确邮箱+密码登录返回 200 + tokens
- 错误密码返回 401
- 数据库中
refresh_tokens表有记录
2.4 JWT 认证中间件
- 在
src/middleware/auth.rs实现AuthUser、AdminUser、AuthorUser提取器 cargo build通过
验收:
- 带 Bearer Token 请求受保护接口 → 200
- 不带 Token → 401
2.5 Token 刷新
- 定义
RefreshRequest - 实现
refresh(token rotation) - 实现
POST /api/v1/auth/refreshhandler cargo build通过
验收:
- 有效 refresh_token 刷新返回新 token 对
- 旧 refresh_token 不可重复使用
2.6 登出
- 实现
POST /api/v1/auth/logout(需认证) cargo build通过
验收:登出后 refresh_token 不可用
2.7 用户信息接口
- 定义
UpdateUserRequest和UpdatePasswordRequest并校验 - 实现
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(需 Admin) cargo build通过
验收:
- 各接口返回正确状态码和数据
- 修改密码后旧密码登录失败,新密码登录成功
- 非 Admin 访问用户列表返回 403
2.8 Phase 2 最终验证
cargo fmt --check通过cargo clippy -- -D warnings通过cargo test通过- 手动测试完整认证流程:注册 → 登录 → 访问受保护接口 → 刷新 Token → 登出
- 数据库中数据正确(用户密码已哈希、refresh_token 可查)
Phase 3:文章核心 ✅
3.1 分类 CRUD
- 在
src/models/category.rs定义Category模型和请求结构体 - 实现 model 层:create, find_all, find_by_id, update, delete
- 实现
src/handlers/category.rs:GET/POST/PUT/DELETE - 注册路由
cargo build通过
验收:分类 CRUD 正常,Reader 角色无法创建(403)
3.2 标签 CRUD
- 在
src/models/tag.rs定义Tag模型和CreateTagRequest - 实现 model 层:create, find_all, delete
- 实现
src/handlers/tag.rs:GET/POST/DELETE - 注册路由
cargo build通过
验收:标签 CRUD 正常,重复 name 返回 409
3.3 文章 CRUD
- 在
src/models/post.rs定义完整模型和请求/响应结构体 - 实现 service 层:create, update, delete, get, list(含 slug 自动生成、excerpt 提取)
- 实现
src/handlers/post.rs:完整 CRUD + 权限校验 - 注册路由
cargo build通过
验收:文章 CRUD 正常,权限控制正确,slug 自动生成
3.4 文章-标签关联
- 创建/更新时处理
tag_ids(sync_tags) - 查询详情时返回标签列表
cargo build通过
验收:文章详情返回对应标签
3.5 Markdown 渲染
- 在
src/utils/markdown.rs实现render_markdown(comrak + ammonia) PostResponse包含html_content字段cargo build通过
验收:Markdown 正确渲染为 HTML(h1 + strong 等)
3.6 分页工具
PaginationParams使用 axum Query 提取- page 默认 1,page_size 默认 20,最大 100
- 所有列表接口统一使用
cargo build通过
验收:分页参数正确
3.7 Phase 3 最终验证
cargo fmt --check通过cargo clippy -- -D warnings通过cargo test通过- 完整流程:创建分类 → 创建标签 → 创建文章 → 列表 → 详情 → 更新 → 删除
Phase 4:评论系统 ✅
4.1 评论数据模型与 CRUD
- 在
src/models/comment.rs定义Comment模型、CreateCommentRequest - 实现
src/services/comment.rs:create, find_approved_by_post, delete - 实现
src/handlers/comment.rs:游客和登录用户分开的评论接口 - 注册路由
cargo build通过
验收:游客评论关联 nickname,登录用户关联 author_id
4.2 嵌套评论
CreateCommentRequest支持parent_id字段- 验证
parent_id对应评论存在且属于同一文章 - 限制嵌套深度不超过 3 层
- 查询时构建树形结构返回(
build_tree) cargo build通过
验收:嵌套回复正常显示在 replies 字段中
4.3 评论审核
- 评论默认 status = "pending"
- 实现
PUT /api/v1/comments/{id}/status(需 Admin) - 公开评论列表仅返回 status = "approved" 的评论
cargo build通过
验收:pending 不可见 → approve 后可见
4.4 Phase 4 最终验证
cargo fmt --check通过cargo clippy -- -D warnings通过cargo test通过
Phase 5:媒体与辅助功能 ✅
5.1 图片上传
- 在
src/models/media.rs定义Media模型 - 实现
src/services/media.rs:save_file— 验证文件类型和大小,保存到 UPLOAD_DIR,生成唯一文件名,写数据库find_all— 分页列表delete_media— 删除文件和数据库记录
- 实现
src/handlers/media.rs:POST /api/v1/media/upload(需认证)— multipart 上传GET /api/v1/media(需认证)DELETE /api/v1/media/{id}(需认证,仅本人或 Admin)
- 挂载
tower_http::services::ServeDir提供/uploads/静态文件访问 - 注册路由
cargo build通过
验收:
- 上传图片返回 URL,文件存在于 uploads 目录
- 超过 5MB 返回 400
- 非图片类型返回 400
5.2 RSS 订阅
- 在
src/handlers/rss.rs实现GET /feed.xml:- 查询最新 20 篇 published 文章
- 使用
rsscrate 生成 RSS 2.0 XML - 返回
Content-Type: application/xml
- 注册路由
cargo build通过
验收:curl /feed.xml 返回合法 RSS 2.0 XML,包含最新文章
5.3 全文搜索(基础版)
GET /api/v1/posts增加q查询参数- 使用 SQLite
LIKE '%keyword%'搜索 title 和 content cargo build通过
验收:GET /api/v1/posts?q=rust 返回标题或正文包含 "rust" 的文章
5.4 浏览量统计
- 在
src/services/post.rs的find_by_slug中增加浏览量 +1 - 基于简化方案:每次访问 detail 接口 view_count += 1(不做去重)
PostResponse包含view_count字段cargo build通过
验收:多次访问文章详情,view_count 递增
5.5 Phase 5 最终验证
cargo fmt --check通过cargo clippy -- -D warnings通过cargo test通过
Phase 6:安全与优化 ✅
6.1 Rate Limiting 中间件
- 在
src/middleware/rate_limit.rs实现基于 IP 的限流:- 自定义
RateLimiter(tokio::sync::Mutex + HashMap)作为 axum Extension + middleware - 登录接口:10 次/分钟/IP
- 注册接口:5 次/小时/IP
- 评论接口:3 次/分钟/IP
- 通用:60 次/分钟/IP
- 自定义
- 挂载到对应路由组
cargo build通过
验收:超过限制后返回 429 Too Many Requests
6.2 CORS 配置
- 使用
tower_http::cors::CorsLayer配置:- 开发环境允许 Any origin(生产应改为白名单)
- 允许的方法:Any
- 允许的头部:Any
cargo build通过
验收:跨域请求返回正确的 CORS 头
6.3 输入校验完善
- 所有 POST/PUT 接口的请求体使用
validatorderive 校验(含UpdateCommentStatusRequest) - Handler 中统一调用
validate(&req)?,校验失败返回 400 + 具体错误信息 cargo build通过
验收:发送空 body 或非法字段返回 400 + 错误描述
6.4 数据库索引与查询优化
- 在
migrations/002_add_indexes.sql中添加复合索引:idx_posts_status_created— 覆盖文章列表查询idx_posts_status_category— 覆盖按分类筛选idx_posts_status_author— 覆盖按作者筛选idx_comments_post_status— 覆盖评论列表查询idx_media_user_created— 覆盖媒体文件列表查询
- 所有分页查询使用单独 count 查询
cargo build通过
验收:索引覆盖常用查询路径
6.5 Phase 6 最终验证
cargo fmt --check通过cargo clippy -- -D warnings通过cargo test通过- 限流正常工作
- CORS 正常工作
Phase 7:部署 ✅
7.1 Dockerfile
- 编写多阶段 Dockerfile:
- Stage 1:
rust:1.85编译 release - Stage 2:
debian:bookworm-slim运行 - 复制二进制 + migrations 目录
- 暴露 3000 端口
- Stage 1:
docker build -t hello-axum .成功docker run -p 3000:3000 hello-axum启动正常
验收:curl http://localhost:3000/api/v1/health 返回 200
7.2 docker-compose
- 编写
docker-compose.yml:- app 服务(构建镜像、挂载 data + uploads)
docker compose up -d正常启动
验收:docker compose up -d 后健康检查通过
7.3 Nginx 配置
- 编写 Nginx 反向代理配置(
deploy/nginx.conf) - 配置 gzip、静态文件缓存、client_max_body_size
- 文档中包含配置说明
验收:配置文件语法正确(nginx -t)
7.4 CI/CD
- 编写
.github/workflows/ci.yml:- 触发条件:push / PR to main
- 步骤:
cargo fmt --check→cargo clippy -- -D warnings→cargo test - 含 cargo cache 加速
验收:CI workflow 语法正确,本地 act 可执行(或推送到 GitHub 后验证)
7.5 Phase 7 最终验证
- Docker 构建和运行正常
- docker-compose 启动正常
- 所有 Phase 1-6 的验收项仍然通过
cargo fmt --check && cargo clippy -- -D warnings && cargo test全通过