1. 项目概述与技术选型
最近在做一个前后端分离的博客系统,前端用Vue3,后端选择了Python的FastAPI框架。这个组合在实际开发中表现非常出色,Vue3的Composition API让代码组织更清晰,FastAPI则提供了高性能的异步支持。下面我会详细介绍整个项目的架构设计和实现细节。
选择Vue3是因为它比Vue2有更好的性能,特别是虚拟DOM的优化和更小的打包体积。Composition API的引入让逻辑复用变得更容易,代码也更容易维护。后端之所以选择FastAPI而不是Django,主要是考虑到博客系统对API响应速度的要求较高,FastAPI的异步特性正好能满足这个需求。
数据库方面我选择了PostgreSQL,因为它对JSON数据的原生支持很适合博客系统的内容存储,而且事务处理性能也很优秀。对于中小型博客来说,MySQL也是个不错的选择,但PostgreSQL在一些高级特性上更胜一筹。
2. 前后端分离架构设计
2.1 前端架构
前端项目使用Vite作为构建工具,相比传统的Webpack,Vite的启动速度和热更新都快得多。项目结构大致如下:
code复制src/
├── assets/ # 静态资源
├── components/ # 公共组件
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia状态管理
├── styles/ # 全局样式
├── utils/ # 工具函数
└── views/ # 页面组件
2.2 后端API设计
后端采用RESTful API设计,主要端点包括:
/api/auth/- 认证相关/api/users/- 用户管理/api/articles/- 文章管理/api/comments/- 评论管理/api/tags/- 标签管理
每个端点都遵循标准的HTTP方法:
- GET - 获取资源
- POST - 创建资源
- PUT/PATCH - 更新资源
- DELETE - 删除资源
2.3 跨域解决方案
前后端分离必然会遇到跨域问题。在后端FastAPI中,我配置了CORSMiddleware:
python复制from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # 前端开发地址
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
前端axios的配置如下:
javascript复制import axios from 'axios'
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
3. 核心功能实现
3.1 用户认证系统
认证采用JWT方案,用户登录后后端返回access_token和refresh_token。前端将token存储在Pinia和localStorage中。
后端认证代码示例:
python复制from datetime import datetime, timedelta
from jose import jwt
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
前端Pinia store管理用户状态:
javascript复制import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
token: localStorage.getItem('token') || null
}),
actions: {
async login(credentials) {
const response = await api.post('/auth/login', credentials)
this.token = response.data.access_token
localStorage.setItem('token', this.token)
await this.fetchUser()
},
async fetchUser() {
const response = await api.get('/auth/me')
this.user = response.data
}
}
})
3.2 文章管理系统
文章支持Markdown格式,前端使用Vditor编辑器,后端将Markdown转换为HTML存储。
编辑器组件实现:
vue复制<template>
<div class="editor">
<vditor :value="content" @input="updateContent" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Vditor from 'vditor'
const content = ref('')
const updateContent = (newContent) => {
content.value = newContent
}
</script>
后端Markdown处理:
python复制import markdown
from markdown.extensions.toc import TocExtension
def markdown_to_html(content):
return markdown.markdown(
content,
extensions=[
'fenced_code',
'codehilite',
TocExtension(baselevel=2),
'tables'
]
)
3.3 评论与标签系统
评论采用嵌套结构,支持回复功能。数据库设计如下:
python复制class Comment(Base):
__tablename__ = "comments"
id = Column(Integer, primary_key=True)
content = Column(Text)
article_id = Column(Integer, ForeignKey("articles.id"))
parent_id = Column(Integer, ForeignKey("comments.id"))
replies = relationship("Comment", backref=backref("parent", remote_side=[id]))
标签系统使用多对多关系:
python复制article_tag = Table(
"article_tag",
Base.metadata,
Column("article_id", Integer, ForeignKey("articles.id")),
Column("tag_id", Integer, ForeignKey("tags.id"))
)
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
4. 部署与优化
4.1 前端部署优化
Vite构建配置:
javascript复制import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
})
4.2 后端部署
使用Uvicorn作为ASGI服务器:
bash复制uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
生产环境建议使用Gunicorn管理Uvicorn worker:
bash复制gunicorn -k uvicorn.workers.UvicornWorker -w 4 main:app
4.3 Nginx配置
Nginx作为反向代理,同时处理静态文件:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
root /path/to/frontend/dist;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static {
alias /path/to/backend/static;
}
}
5. 开发经验与问题解决
5.1 常见问题
- 跨域问题:除了后端配置CORS外,开发时可以在Vite中配置代理:
javascript复制export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true
}
}
}
})
- JWT过期处理:实现自动刷新token的逻辑:
javascript复制api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
const authStore = useAuthStore()
await authStore.refreshToken()
return api(originalRequest)
}
return Promise.reject(error)
}
)
- Markdown图片处理:将图片上传到对象存储并替换链接:
python复制import re
from pathlib import Path
def process_images(content):
def upload_and_replace(match):
image_data = match.group(2)
# 上传逻辑
new_url = upload_to_oss(image_data)
return f""
return re.sub(r'!\[(.*?)\]\((.*?)\)', upload_and_replace, content)
5.2 性能优化建议
- 前端懒加载:按需加载组件和路由
javascript复制const ArticleDetail = () => import('./views/ArticleDetail.vue')
- 后端缓存策略:对频繁访问的数据添加缓存
python复制from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
@app.on_event("startup")
async def startup():
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
- 数据库查询优化:使用selectinload避免N+1查询
python复制from sqlalchemy.orm import selectinload
articles = db.query(Article).options(selectinload(Article.comments)).all()
6. 扩展功能实现
6.1 全文搜索
使用PostgreSQL的全文搜索功能:
python复制from sqlalchemy import func
search_query = "Vue3 Python"
articles = db.query(Article).filter(
func.to_tsvector('english', Article.title + ' ' + Article.content)
.op('@@')(func.to_tsquery('english', search_query))
).all()
6.2 文章版本控制
实现文章编辑历史记录:
python复制class ArticleVersion(Base):
__tablename__ = "article_versions"
id = Column(Integer, primary_key=True)
article_id = Column(Integer, ForeignKey("articles.id"))
content = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
6.3 数据统计与分析
使用PostgreSQL的窗口函数生成阅读趋势:
python复制from sqlalchemy import over, func
trends = db.query(
Article.title,
func.count(ArticleView.id).over(
partition_by=Article.id,
order_by=ArticleView.created_at
).label('views_trend')
).join(ArticleView).all()
在实际开发中,这个Vue3+Python的博客系统架构表现非常稳定。前端Vue3的响应式系统让UI开发变得高效,后端的FastAPI则提供了出色的性能。通过合理的架构设计和优化,系统可以轻松应对日均上万的访问量。