个人博客系统 技术方案
个人博客系统 技术方案
文档版本:V1.0 作者:王清国 日期:2026年7月 关联文档:《个人博客系统 PRD》V1.0
本文是对 PRD 第七章「技术方案」的展开与落地设计,作为后续(含 AI 辅助)编码的统一规范依据。PRD 定义「做什么」,本文定义「怎么做」。
一、总体架构
1.1 架构总览
三端(后台管理 / Web / 小程序)共享同一套后端 API 与数据库,内容一次录入、多端同步展示。
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 后台管理 admin │ │ Web 端 │ │ 小程序端 │
│ Vue3 + Vite │ │ Next.js SSR │ │ 微信原生 │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ 管理员 JWT │ 访客 JWT │ 访客 JWT
└──────────────────┼───────────────────┘
│ HTTPS / RESTful JSON
┌──────▼───────┐
│ Nginx 反代 │ 静态资源 / TLS / 路由分发
└──────┬───────┘
┌──────▼───────┐
│ FastAPI 应用 │ auth/articles/comments/…
│ (Uvicorn) │
└──┬────────┬───┘
┌─────────▼──┐ ┌──▼────────┐ ┌──────────────┐
│ PostgreSQL │ │ Redis │ │ 对象存储 OSS/COS │
│ 业务数据 │ │ 缓存/令牌 │ │ 图片/视频/音频 │
└────────────┘ └───────────┘ └──────────────┘
1.2 技术选型总表
| 层级 | 选型 | 说明 |
|---|---|---|
| 后端框架 | Python 3.11+ / FastAPI | 类型注解友好、异步、自动 OpenAPI 文档 |
| ORM | SQLModel | Pydantic + SQLAlchemy,模型即 schema |
| 数据库 | PostgreSQL 16 | 原生 JSON/JSONB、全文检索、成熟稳定 |
| 缓存 | Redis 7 | 微信令牌缓存、JWT 黑名单、限流、阅读量计数 |
| 数据库迁移 | Alembic | 版本化 schema 变更 |
| Web 端 | Next.js 14(App Router,React) | SSR/SSG 利于 SEO |
| 后台管理 | Vue3 + Vite + TypeScript | 精简自定义,不引入完整中后台模板 |
| 小程序 | 微信小程序原生 | 复用已有经验 |
| 媒体存储 | 对象存储(COS/OSS/七牛,可配置) | 存储层接口抽象,开源者可切换 |
| 部署 | Docker Compose + Nginx | 环境一致、一键起项目、便于迁移 |
| 密码哈希 | bcrypt(passlib) | 管理员密码加密存储 |
| 内容安全 | 微信 msgSecCheck / 可插拔审核 | 评论违规过滤 |
二、后端设计(blog-system-backend)
2.1 分层与目录结构
按 PRD「面向开源、模块独立」要求,后端按**业务模块(feature-based)**组织,而非按技术层横切。每个业务模块内部再分 router / service / models / schemas。
blog-system-backend/
├── app/
│ ├── main.py # FastAPI 实例、路由注册、中间件
│ ├── core/ # 跨模块基础设施
│ │ ├── config.py # Pydantic Settings,读环境变量
│ │ ├── database.py # 引擎、Session 依赖
│ │ ├── redis.py # Redis 连接
│ │ ├── security.py # JWT 签发/校验、bcrypt
│ │ ├── deps.py # 通用依赖(当前用户、分页、权限)
│ │ ├── response.py # 统一响应封装
│ │ └── exceptions.py # 统一异常与错误码
│ ├── modules/ # ★ 业务模块,各自独立可裁剪
│ │ ├── auth/ # 管理员登录 + 微信 OAuth + 小程序登录
│ │ ├── users/
│ │ ├── articles/ # 文章 + 分类 + 标签
│ │ ├── comments/ # 评论 + 内容安全
│ │ ├── photography/ # 摄影作品
│ │ ├── projects/ # 项目展示
│ │ ├── music/ # 背景音乐
│ │ └── profile/ # 个人主页信息(含 MBTI/运动数据,可裁剪)
│ ├── storage/ # 对象存储抽象层(见 §5)
│ └── integrations/ # 微信 API 客户端封装
│ └── wechat/
├── migrations/ # Alembic
├── tests/
├── pyproject.toml # 依赖(推荐 uv / poetry)
├── .env.example # 配置样例(不含真实密钥)
├── docker-compose.yml
└── Dockerfile
模块划分原则:auth、profile、music 等个性化/可选模块的路由在 main.py 中集中注册,开源者可通过删除模块目录 + 注释一行注册代码来裁剪,不影响其余模块。
2.2 分层职责
- router:只做请求解析、依赖注入、调用 service、返回响应,不含业务逻辑。
- service:业务逻辑与事务边界,接收/返回领域对象或 schema。
- models:SQLModel 表模型(
table=True)。 - schemas:请求/响应 DTO(
table=False的 SQLModel 或 Pydantic 模型),与表模型解耦,避免把内部字段暴露给前端。
2.3 配置管理
core/config.py 用 pydantic-settings 从环境变量加载,所有敏感项(数据库、Redis、微信 AppID/Secret、JWT 密钥、对象存储密钥)只从环境变量读取,仓库仅提交 .env.example。
class Settings(BaseSettings):
database_url: PostgresDsn
redis_url: RedisDsn
jwt_secret: str
jwt_access_ttl: int = 3600
jwt_refresh_ttl: int = 60 * 60 * 24 * 30
wechat_web_appid: str # 网站应用
wechat_web_secret: str
wechat_mp_appid: str # 小程序
wechat_mp_secret: str
storage_provider: str = "cos" # cos / oss / qiniu / local
# …对象存储 bucket / region / ak / sk
model_config = SettingsConfigDict(env_file=".env")
三、数据库设计
在 PRD 第六章草案基础上补充索引、约束与审计字段。所有表统一含 created_at,可变实体加 updated_at。
3.1 表清单与关键设计
| 表 | 关键点 |
|---|---|
users | role 枚举(admin/visitor);wechat_unionid 建唯一索引(打通两端身份,见 §4.4);wechat_openid 可空;username 唯一(仅管理员)。 |
articles | status 枚举(draft/published);category_id 外键;published_at、status 建索引供列表筛选;content 存 Markdown 原文。view_count 见 §6.3。 |
categories | 文章分类,name 唯一、slug 唯一(用于 SEO URL)。 |
tags / article_tags | 多对多;article_tags 联合主键 (article_id, tag_id)。 |
photography_works | media_type 枚举(photo/video);media_url、cover_image(视频封面);category 字符串分类。 |
projects | gallery 用 JSONB 存图片列表。 |
background_music | is_default 布尔,通过部分唯一索引保证「至多一条默认」。 |
comments | article_id/user_id 外键;status 枚举(normal/hidden);(article_id, created_at) 联合索引供分页。 |
profile | 单条记录(约定 id=1);sports_data、其他个性化字段用 JSONB,便于扩展与裁剪。 |
3.2 URL 友好标识(SEO)
文章与分类增加 slug 字段,Web 端详情页使用 /articles/{slug} 而非纯数字 id,提升可读性与 SEO。
3.3 迁移
用 Alembic 管理 schema 版本;初始化脚本内置管理员账号创建命令(读取环境变量的初始用户名/密码,bcrypt 哈希后落库),避免手工写 SQL。
四、认证与鉴权
这是全系统安全敏感度最高、且两端逻辑不同的模块。独立为 modules/auth,与业务逻辑解耦。
4.1 统一令牌方案(JWT + Redis)
三端登录成功后统一签发 JWT:
- Access Token:短时效(默认 1h),无状态,
payload含sub(user_id)、role、exp。 - Refresh Token:长时效(默认 30d),存 Redis(
refresh:{user_id}:{jti}),支持主动吊销。 - 登出/踢下线:将 access token 的
jti写入 Redis 黑名单,TTL 等于其剩余有效期。
鉴权用 FastAPI 依赖实现:
async def get_current_user(token=Depends(oauth2_scheme)) -> User: ...
async def require_admin(user=Depends(get_current_user)) -> User:
if user.role != Role.admin: raise ForbiddenError()
return user
管理端接口挂 require_admin,评论等挂 get_current_user,公开内容不挂依赖。
4.2 管理员登录(账号密码)
POST /api/auth/admin/login → 校验 username + bcrypt 校验 password_hash → 签发 JWT。仅允许 role=admin 用户。可加登录失败次数限流(Redis 计数)。
4.3 Web 端微信登录(网站应用 OAuth2.0)
- 前端跳转微信扫码授权页(
open.weixin.qq.com),带appid+redirect_uri+state。 - 微信回调携带
code→ 前端调POST /api/auth/wechat/web,传code。 - 后端用
code换access_token+openid+unionid,再拉用户信息(昵称/头像)。 - 按
unionid查/建用户 → 签发 JWT。
4.4 小程序登录(静默)
- 小程序
wx.login()拿code。 POST /api/auth/wechat/mp传code→ 后端code2session换openid+unionid+session_key。- 按
unionid查/建用户 → 签发 JWT。
4.5 unionid 打通身份
同一微信用户在 Web 与小程序拿到相同 unionid(需两端 AppID 绑定在同一微信开放平台账号下)。用户查找与创建一律以 unionid 为主键依据:先按 unionid 命中则复用,仅补记对应端的 openid;未命中才创建新用户。这样避免同一人重复注册两条记录。
4.6 微信 access_token 缓存
微信全局 access_token(调 msgSecCheck 等接口需要)有 7200s 有效期且有调用频率限制,统一缓存到 Redis(wechat:access_token),过期前复用,由 integrations/wechat 客户端封装刷新逻辑。
五、媒体存储方案
摄影作品、视频、背景音乐等文件走对象存储,存储层抽象为统一接口,生产用云厂商、开源者可切本地。
5.1 存储抽象接口
class StorageBackend(Protocol):
async def presign_upload(self, key: str, content_type: str) -> UploadTicket: ...
def public_url(self, key: str) -> str: ...
async def delete(self, key: str) -> None: ...
storage/ 下提供 cos.py / oss.py / qiniu.py / local.py 实现,由 settings.storage_provider 工厂选择。数据库只存 key(对象键),对外 URL 由后端按当前 provider 拼接,便于日后迁移存储桶。
5.2 上传流程(前端直传)
管理端上传大文件(尤其视频)时,后端签发预签名上传凭证,前端直传对象存储,避免文件流经应用服务器占用带宽:
管理端请求上传凭证 → 后端校验管理员权限、生成 key + presign → 前端直传 OSS → 前端把返回的 key 提交给业务接口落库。
本地 provider 降级为「后端接收 multipart → 存本地目录 → Nginx 托管」。
5.3 媒体处理
- 图片:上传后可异步生成缩略图(画廊列表用),或依赖对象存储的图片处理样式(COS/OSS 均支持 URL 参数缩放)。
- 视频:要求管理端提供
cover_image封面,或调用云厂商截帧能力。
六、API 设计规范
6.1 通用约定
- 前缀
/api,RESTful 风格,资源名复数。管理端接口置于/api/admin/*便于 Nginx 与鉴权区分。 - 请求/响应统一 JSON;时间统一 ISO8601(UTC)。
- 充分利用 FastAPI 自动生成的 Swagger(
/docs)作为契约与联调依据。
6.2 统一响应与错误码
// 成功
{ "code": 0, "message": "ok", "data": { /* ... */ } }
// 失败
{ "code": 40101, "message": "登录已过期", "data": null }
错误码分段:4010x 认证、4030x 权限、4220x 参数校验、4290x 限流、5000x 服务端。通过统一异常处理器(core/exceptions.py)拦截并格式化。
6.3 分页与列表
列表统一 ?page=&size= 或游标分页,响应含 total。文章、评论、摄影作品列表均遵循同一分页结构。
阅读量 view_count:详情访问先在 Redis 自增计数,定时批量回写 PostgreSQL,避免高频写库。
6.4 接口先行
遵循 PRD 建议:先定 OpenAPI 契约再实现。用 SQLModel 定义 schema 后,FastAPI 自动产出 OpenAPI,作为三端前端并行开发的契约来源。
七、内容安全(评论审核)
评论为 P0 且必须过审。发表流程:
- 认证用户提交评论。
- 后端调微信
msgSecCheck(文本)做内容安全检测。 - 通过 →
status=normal落库并展示;命中违规 →status=hidden或拒绝,管理员可在后台复核。
审核逻辑封装在 comments/service 调用 integrations/wechat,并设计成可插拔(ContentModerator 接口),开源者可替换为第三方审核或关闭。
八、各前端技术方案
8.1 Web 端(Next.js,重 SEO)
- App Router;文章列表/详情、摄影、项目等公开页用 SSR 或 SSG + ISR,保证搜索引擎可收录。
- 详情页走
slugURL,生成<title>/<meta>/OG 标签、sitemap.xml、robots.txt。 - 评论区、背景音乐播放器等交互部分为客户端组件;未登录可浏览,评论触发微信 OAuth。
- 数据获取通过后端
/api;服务端渲染时用服务端 fetch。
8.2 后台管理(Vue3 + Vite)
- Vue3
<script setup>+ TypeScript + Vue Router + Pinia;UI 可选 Element Plus / Naive UI(仅作组件库,不用完整 admin 模板)。 - 页面:登录、文章管理(含 Markdown 编辑器)、摄影作品管理(含直传上传)、项目管理、评论审核、分类/标签管理、背景音乐管理、个人主页信息编辑。
- Axios 封装:请求带
Authorization: Bearer,401 触发 refresh,失败跳登录。 - 纯前端 SPA,构建产物由 Nginx 托管,仅管理员访问(可加 IP/路径限制)。
8.3 小程序端
- 与 Web 端功能对齐,UI 适配小程序规范。
- 启动时
wx.login()静默登录换 JWT,存本地 storage,请求统一封装带 token。 - 图片/视频用小程序原生组件;背景音乐用
BackgroundAudioManager。 - 评论同样需认证用户,提交前经后端内容安全检测。
九、部署方案(Docker Compose)
9.1 容器编排
services:
nginx # 反代 + TLS + 静态资源(web/admin 构建产物)
backend # FastAPI (Uvicorn/Gunicorn),多 worker
postgres # 数据卷持久化
redis # 缓存/令牌
web(Next.js)可独立容器化(Node 运行 SSR)或部署到 Vercel/静态托管,由 Nginx 统一入口分流。admin为静态产物,构建后交 Nginx 托管。- 所有密钥经
.env注入 compose,不写进镜像。 - 数据卷持久化 PostgreSQL 与上传目录(若用 local provider)。
9.2 Nginx 路由
/api/*→ backend/(Web 公开站)→ Next.js 容器 / 静态/admin/*→ admin 静态产物- TLS 证书(Let's Encrypt),全站 HTTPS。
9.3 环境分层
.env.example 提交模板;.env(本地)、.env.production(服务器)不入库。开源者 cp .env.example .env + 填值 + docker compose up 即可起全栈。
十、安全与非功能性需求
- 密码:bcrypt 哈希,绝不明文;管理员登录失败限流。
- 传输:全站 HTTPS。
- 鉴权:管理端接口强制
require_admin;写操作校验归属。 - 限流:评论发表、登录等敏感接口基于 Redis 滑动窗口限流。
- 注入防护:SQLModel/SQLAlchemy 参数化查询;输入用 Pydantic 校验。
- XSS:文章 Markdown 渲染时做白名单净化(前端 sanitize);评论转义。
- 密钥隔离:微信 AppSecret、JWT 密钥、存储密钥全部环境变量注入(PRD 第九章要求)。
- CORS:仅放行三端可信域名。
- 性能:FastAPI 全异步;热点读走 Redis 缓存;个人量级无需过度设计但保留扩展余量。
十一、面向开源的可裁剪性设计
呼应 PRD 第九章:
- 模块级可裁剪:
profile(MBTI/运动数据)、music、photography等个性化模块目录独立,删除目录 + 摘除一行路由注册即可移除,核心的文章/评论不受影响。 - 配置驱动:存储 provider、内容审核 provider、微信开关等通过环境变量切换,fork 者无需改代码。
- 无硬编码密钥:仓库只含
.env.example。 - 文档:每仓库配 README + 部署文档;后端保留完整 Swagger。
- 许可证与贡献指南待项目稳定后补充,不阻塞 MVP。
十二、开发里程碑
对齐 PRD 路线图,MVP 聚焦 P0:
| 阶段 | 内容 |
|---|---|
| M1 契约先行 | 定稿数据库 schema(SQLModel)+ Alembic 初始迁移 + OpenAPI 草案;搭 Docker Compose 基础设施 |
| M2 后端核心 | auth(管理员+双端微信+unionid 打通)、articles、comments(含内容安全)、媒体直传;Swagger 联调 |
| M3 Web 端 | Next.js 公开站(主页/文章/摄影/项目/评论),SEO 与 SSR |
| M4 后台管理 | Vue3 管理端全部 CRUD |
| M5 小程序 | 功能对齐 + 静默登录 + 评论 |
| M6 部署上线 | Nginx/TLS、生产 compose、管理员初始化、冒烟验证 |
| V2+ | 运动数据对接、项目展示优化、评论点赞、文章内搜索(PostgreSQL 全文检索) |
附:关键技术决策记录
| 决策 | 选择 | 理由 |
|---|---|---|
| 后台管理框架 | Vue3 + Vite | 模板语法上手快、后台 CRUD 开发高效 |
| 媒体存储 | 对象存储 + 抽象层可配置 | CDN 加速、不占 VPS 磁盘、开源可切换 |
| 部署 | Docker Compose | 环境一致、一键起项目、迁移方便 |
| 缓存 | 引入 Redis | 微信令牌缓存、JWT 吊销、限流、阅读量计数 |
| 令牌 | JWT(access)+ Redis(refresh/黑名单) | 无状态鉴权兼顾可吊销性 |
| 身份打通 | 以 unionid 为准 | 同一微信用户双端不重复注册 |