Day 6 详细学习计划:后端项目结构优化
学习目标
- 完善后端项目结构
- 优化 API 接口组织
- 准备前端对接
知识点详解
1. 项目结构优化
重要性:
- 提高代码可维护性
- 便于团队协作
- 符合最佳实践
推荐的项目结构:
project/
├── main.py # 应用入口
├── database.py # 数据库配置
├── models/ # 数据模型
│ ├── __init__.py
│ ├── article.py # 文章模型
│ └── user.py # 用户模型(未来扩展)
├── schemas/ # Pydantic 模型
│ ├── __init__.py
│ ├── article.py # 文章 Schema
│ └── user.py # 用户 Schema
├── crud/ # 数据库操作
│ ├── __init__.py
│ └── article.py # 文章 CRUD 操作
├── api/ # API 路由
│ ├── __init__.py
│ ├── api.py # API 路由整合
│ └── v1/ # API 版本
│ ├── __init__.py
│ ├── articles.py # 文章相关路由
│ └── users.py # 用户相关路由
└── utils/ # 工具函数
├── __init__.py
└── markdown_utils.py # Markdown 处理工具
2. CRUD 模块化
好处:
- 业务逻辑与路由分离
- 提高代码复用性
- 便于单元测试
练习代码
重构后的项目结构
main.py
python
from contextlib import asynccontextmanager
from fastapi import FastAPI # 导入FastAPI框架
from fastapi.middleware.cors import CORSMiddleware # 导入CORS中间件,用于处理跨域请求
from api.v1.api import api_router
from database import create_db_and_tables # 导入API路由
@asynccontextmanager
async def lifespan(app: FastAPI):
# 应用启动时创建数据库表
create_db_and_tables()
yield
# 应用关闭时可以执行清理工作
app = FastAPI(title="Tutorial Site API", version="1.0.0", lifespan=lifespan) # 创建FastAPI应用实例,设置标题和版本
# 添加 CORS 中间件
app.add_middleware(
CORSMiddleware, # 使用CORS中间件
allow_origins=["*"], # 允许所有来源的跨域请求
allow_credentials=True, # 允许携带凭证信息
allow_methods=["*"], # 允许所有HTTP方法
allow_headers=["*"], # 允许所有请求头
)
# 包含 API 路由
app.include_router(api_router, prefix="/api/v1") # 包含API路由,并设置路由前缀为/api/v1
@app.get("/") # 定义根路径的GET请求处理函数
def read_root(): # 根路径处理函数
return {"message": "Welcome to Tutorial Site API"} # 返回欢迎信息
database.py
python
from sqlmodel import SQLModel, create_engine, Session
from typing import Generator
DATABASE_URL = "sqlite:///./tutorial.db"
engine = create_engine(DATABASE_URL, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def get_session() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session
models/article.py
python
from sqlmodel import SQLModel, Field
from typing import Optional
from datetime import datetime
class Article(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
content: str
author: Optional[str] = None
published: bool = False
created_at: Optional[datetime] = None
schemas/article.py
python
from sqlmodel import SQLModel
from typing import Optional
from datetime import datetime
class ArticleBase(SQLModel):
title: str
content: str
author: Optional[str] = None
published: bool = False
class ArticleCreate(ArticleBase):
pass
class ArticleUpdate(ArticleBase):
title: Optional[str] = None
content: Optional[str] = None
class ArticleRead(ArticleBase):
id: int
created_at: Optional[datetime] = None
crud/article.py
python
from sqlmodel import Session, select
from models.article import Article
from schemas.article import ArticleCreate, ArticleUpdate
from typing import List, Optional
def create_article(session: Session, article_create: ArticleCreate) -> Article:
db_article = Article.from_orm(article_create)
session.add(db_article)
session.commit()
session.refresh(db_article)
return db_article
def get_articles(session: Session) -> List[Article]:
articles = session.exec(select(Article)).all()
return articles
def get_article_by_id(session: Session, article_id: int) -> Optional[Article]:
article = session.get(Article, article_id)
return article
def update_article(session: Session, article_id: int, article_update: ArticleUpdate) -> Optional[Article]:
article = session.get(Article, article_id)
if not article:
return None
article_data = article_update.dict(exclude_unset=True)
for key, value in article_data.items():
setattr(article, key, value)
session.add(article)
session.commit()
session.refresh(article)
return article
def delete_article(session: Session, article_id: int) -> bool:
article = session.get(Article, article_id)
if not article:
return False
session.delete(article)
session.commit()
return True
api/v1/articles.py
python
from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import Session
from typing import List
from database import get_session
from crud.article import create_article, get_articles, get_article_by_id, update_article, delete_article
from schemas.article import ArticleCreate, ArticleRead, ArticleUpdate
router = APIRouter(prefix="/articles", tags=["articles"])
@router.post("/", response_model=ArticleRead, status_code=status.HTTP_201_CREATED)
def create_new_article(*, session: Session = Depends(get_session), article: ArticleCreate):
return create_article(session, article)
@router.get("/", response_model=List[ArticleRead])
def read_all_articles(*, session: Session = Depends(get_session)):
return get_articles(session)
@router.get("/{article_id}", response_model=ArticleRead)
def read_single_article(*, session: Session = Depends(get_session), article_id: int):
article = get_article_by_id(session, article_id)
if not article:
raise HTTPException(status_code=404, detail="Article not found")
return article
@router.put("/{article_id}", response_model=ArticleRead)
def update_single_article(
*,
session: Session = Depends(get_session),
article_id: int,
article_update: ArticleUpdate
):
article = update_article(session, article_id, article_update)
if not article:
raise HTTPException(status_code=404, detail="Article not found")
return article
@router.delete("/{article_id}")
def delete_single_article(*, session: Session = Depends(get_session), article_id: int):
success = delete_article(session, article_id)
if not success:
raise HTTPException(status_code=404, detail="Article not found")
return {"ok": True}
api/v1/api.py
python
from fastapi import APIRouter
from api.v1.articles import router as articles_router
api_router = APIRouter()
api_router.include_router(articles_router)
易错点及解决方案
1. 循环导入问题
问题:
模块之间相互导入导致 ImportError
解决方案:
- 重新组织项目结构
- 使用延迟导入
- 将共享部分提取到单独文件
2. 相对导入错误
问题:
ValueError: attempted relative import with no known parent package
解决方案:
确保正确设置 Python 路径或使用绝对导入
3. 数据库连接问题
问题:
多个地方创建数据库连接导致冲突
解决方案:
统一管理数据库引擎和会话
4. API 路由未正确包含
问题:
API 路由无法访问
解决方案:
检查路由是否正确包含,前缀是否正确设置
今天在main.py中少添加了 lifespan=lifespan
- app = FastAPI(title="Tutorial Site API", version="1.0.0", lifespan=lifespan)
导致我找了好半天的问题,无法创建表格,真是恶心坏了。
ai找了半天都没有找到问题在哪里?还给我一顿乱改。
这个是由于版本更新,摒弃旧的方法导致的,正确的main.py前面应该这样写,一定要记得添加
lifespan=lifespan
python
from contextlib import asynccontextmanager
from fastapi import FastAPI # 导入FastAPI框架
from fastapi.middleware.cors import CORSMiddleware # 导入CORS中间件,用于处理跨域请求
from api.v1.api import api_router
from database import create_db_and_tables # 导入API路由
@asynccontextmanager
async def lifespan(app: FastAPI):
# 应用启动时创建数据库表
create_db_and_tables()
yield
# 应用关闭时可以执行清理工作
app = FastAPI(title="Tutorial Site API", version="1.0.0", lifespan=lifespan) # 创建FastAPI应用实例,设置标题和版本