Skip to content

Repository files navigation

CaoxuBlog 后端

测试工程师个人技术博客 —— 代码永远在等待一个它从未通过的测试

基于 FastAPI + SQLAlchemy 2.0(async)+ PostgreSQL / SQLite + Redis 的后端服务,配套 Nuxt 3(SSR) 前端(见 caoxublog-web/)。

  • P1:数据模型 + Alembic 迁移 + 博客/教程 CRUD + 标签多对多
  • P2:首页去重、全文搜索、阅读/点赞/评论互动(Redis 去重与频次限制)、敏感词过滤
  • P3:Nuxt 三栏 + 教程三层目录 + Sitemap + SEO(caoxublog-web/
  • P4:暗黑模式 + 鼠标光点 + Markdown 高亮/行号/复制
  • P5:边界测试套件 + README 自动生成 + CI

技术栈(锁定版本)

依赖版本
后端fastapi>=0.115,<0.116
后端uvicorn[standard]>=0.30,<0.35
后端sqlalchemy[asyncio]>=2.0,<2.1
后端asyncpg>=0.30,<0.31
后端aiosqlite>=0.20,<0.22
后端alembic>=1.13,<1.16
后端pydantic>=2.7,<3
后端pydantic-settings>=2.3,<3
后端redis>=5,<7
后端pyahocorasick>=2,<3
前端@nuxtjs/tailwindcss6.12.2
前端@playwright/test^1.48.0
前端@types/markdown-it^14.1.2
前端highlight.js^11.12.0
前端markdown-it^14.1.0
前端nuxt^3.13.0
前端tailwindcss^3.4.0
前端typescript^5.6.0
前端vue^3.5.0
前端vue-router^4.4.0

快速开始

# 1. 创建虚拟环境并安装依赖
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linuxsource .venv/bin/activate
pip install -e ".[dev]"# 2. 准备环境变量(可选,默认已用 SQLite)
cp .env.example .env
# 3. 执行数据库迁移
alembic upgrade head
# 4. 灌入演示数据(2 篇博客、1 篇教程、3 个标签)
python scripts/seed.py
# 5. 启动服务
uvicorn app.main:app --reload

启动后访问:

数据库切换(DATABASE_URL

数据库连接串通过环境变量 DATABASE_URL 切换,支持双后端:

环境连接串
开发(SQLite,开箱即用)sqlite+aiosqlite:///./caoxublog.db
生产(PostgreSQL 16)postgresql+asyncpg://USER:PASS@HOST:5432/caoxublog
# 切到 PostgreSQLexport DATABASE_URL="postgresql+asyncpg://caoxu:secret@localhost:5432/caoxublog"
alembic upgrade head

说明:主键类型使用 BigInteger().with_variant(Integer, "sqlite"), PostgreSQL 下为 BIGINT(符合 SRS §5.1),SQLite 下退回 INTEGER 以支持自增。

数据表

表名模型类说明
commentsComment评论模型(SRS §5.1 comments 表)。
post_tags-博客 / 教程与标签的多对多关联表。
postsPost博客 / 教程文章模型(Post 统一承载 blog 与 tutorial)。
tagsTag标签模型。
usersUser用户模型(预留微信登录,当前不开放自助注册)。

API 一览

方法路径说明
GET/api/home首页:3 篇置顶 + 6 篇最新(不重复)。
GET/api/posts文章列表(分页 + post_type / status 过滤,软删除不返回)。
POST/api/posts创建文章(博客 / 教程)。
DELETE/api/posts/{post_id}软删除:仅置 is_deleted=True,保留数据可恢复。
GET/api/posts/{post_id}文章详情。
PUT/api/posts/{post_id}更新文章(部分更新)。
GET/api/posts/{post_id}/comments评论列表,时间正序(created_at ASC),可选分页。
POST/api/posts/{post_id}/comments发表评论:先敏感词过滤(422),再频次校验(超限 429)。
POST/api/posts/{post_id}/like点赞(同一 IP 对同一文章仅计一次,重复点赞不累加)。
POST/api/posts/{post_id}/tags给文章绑定一批标签(幂等:已绑定的标签不会重复绑定)。
DELETE/api/posts/{post_id}/tags/{tag_id}解绑文章与单个标签的关联。
POST/api/posts/{post_id}/view阅读量 +1(同一 IP 对同一文章 24h 内仅计一次)。
GET/api/search按关键词搜索已发布文章(标题 / 简介 / 正文),支持类型过滤。
GET/api/tags标签列表(分页)。
POST/api/tags创建标签(重名返回 409)。
DELETE/api/tags/{tag_id}删除标签(先解除与文章的关联,再物理删除)。
GET/api/tags/{tag_id}标签详情。
PUT/api/tags/{tag_id}更新标签(重名返回 409)。

统一分页响应 Page[T]{"items": [...], "total": N}。 统一错误响应:{"detail": "..."}(404/409/429)或 {"detail": [...]}(422)。

前端路由(caoxublog-web/

路径说明
/首页(品牌 + 置顶 + 最新)
/blog博客列表
/blog/:slug博客详情(三栏)
/tutorial教程目录索引
/tutorial/:slug*教程详情(三栏,catch-all 三层)

Redis 与互动去重

互动接口(阅读 / 点赞 / 评论)依赖 Redis 7.x,Key 设计:

Key用途命令
view:{post_id}:{ip}阅读量 24h 去重SET ... EX 86400 NX
like:{post_id}:{ip}点赞去重SET ... NX
comment:{post_id}:{ip}评论频次计数INCR + 过期
# 本地启动 Redis
docker run -d --name caoxublog-redis -p 6379:6379 redis:7
# 或直接使用本机 redis-server

评论次数上限由 COMMENT_LIMIT 控制(默认 3),阅读窗口由 VIEW_DEDUP_SECONDS 控制(默认 86400)。

客户端 IP 提取

get_client_ip 依赖负责解析客户端 IP:

  • 默认(TRUST_PROXY_HEADERS=false):使用直连地址(request.client.host)。
  • 反向代理之后(TRUST_PROXY_HEADERS=true):信任 X-Forwarded-For 首段 / X-Real-IP

安全提示:仅当应用部署在可信反向代理(Nginx 等)之后才应开启 TRUST_PROXY_HEADERS, 否则攻击者可伪造 X-Forwarded-For 绕过阅读/点赞/评论的频率限制。

敏感词库维护

敏感词库位于 app/data/sensitive_words.txt,每行一个词,# 开头为注释。

  • 使用 pyahocorasick 构建 Aho-Corasick 自动机,多模式匹配性能优于逐词 replace
  • 词库为进程级懒加载单例:编辑文件后需重启服务才生效(热更新见后续批次)。
  • 命中敏感词的评论直接返回 422,不写入数据库。

运行测试

# 全量
pytest -q
# 覆盖率(要求 ≥ 80%)
pytest --cov=app --cov-report=term-missing
# 重新生成本 README
python scripts/gen_readme.py

工程硬约束(见 AGENTS.md

  1. 依赖注入必须使用 Annotated[AsyncSession, Depends(get_db)]
  2. async 路由禁止同步 ORM / 同步 Session()
  3. 任何删除操作必须软删除(is_deleted=True

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages