1. 项目背景与目标
最近在开发一个基于FastAPI的后端服务时,遇到了权限管理的问题。现有的系统只有简单的登录验证,所有用户登录后都能访问所有接口,这显然不符合实际业务需求。于是决定对系统进行升级,主要实现以下几个目标:
- 在用户模型中添加角色字段(role),区分普通用户和管理员
- 实现接口级别的权限控制,确保只有管理员能访问特定接口
- 重构项目结构,采用标准后端分层架构
- 将注册功能从主文件中拆解出来,提高代码可维护性
这个改造过程涉及到数据库模型修改、权限中间件开发、项目结构重构等多个环节,下面我会详细记录每个步骤的实现细节和注意事项。
2. 环境准备与基础配置
2.1 开发环境
项目基于以下技术栈:
- Python 3.13
- FastAPI 0.95.2
- SQLAlchemy 2.0
- Pydantic 1.10.7
开发工具使用PyCharm,数据库采用SQLite(适合快速开发和测试)。
2.2 初始状态检查
在开始改造前,确保已有功能正常运行:
- 数据库连接正常
- 用户注册/登录功能可用
- JWT token生成和验证正常
- 基础API接口能正确响应
可以通过以下命令启动服务:
bash复制uvicorn main:app --reload
3. 实现角色管理与权限控制
3.1 修改用户数据模型
首先需要在用户表中添加role字段,用于区分用户角色。修改models.py文件:
python复制from sqlalchemy import Column, Integer, String
from app.db.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
phone = Column(String, unique=True, index=True)
password = Column(String)
role = Column(String, default="user") # 新增角色字段
关键点说明:
- role字段类型为String,默认值为"user"
- 后续可以通过修改该字段值为"admin"来提升用户权限
- 由于修改了模型结构,需要重建数据库表
注意:在生产环境中,应该使用数据库迁移工具如Alembic来处理模型变更,而不是直接删除重建数据库。
3.2 重建数据库
由于模型结构发生了变化,最简单的方法是删除旧数据库并让SQLAlchemy重新创建:
- 删除项目目录下的database.db文件
- 重启FastAPI服务,会自动创建新表
验证方法:
- 注册一个新用户
- 检查数据库,确认role字段已存在且默认值为"user"
3.3 实现管理员专属接口
现在可以创建一个只有管理员能访问的接口。在main.py中添加:
python复制from fastapi import HTTPException, Depends
@app.get("/admin")
def admin_only(current_user = Depends(get_current_user)):
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="权限不足")
return {"message": "欢迎管理员"}
同时需要修改get_current_user函数,使其返回完整的用户对象而不仅仅是token payload:
python复制security = HTTPBearer()
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
token = credentials.credentials
payload = verify_token(token)
if payload is None:
raise HTTPException(status_code=401, detail="无效token")
user_id = payload.get("user_id")
if user_id is None:
raise HTTPException(status_code=401, detail="token缺少user_id")
user = db.query(models.User).filter(models.User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
return user
3.4 权限测试流程
- 用普通用户登录获取token
- 访问/admin接口应返回403错误
- 在数据库中手动将该用户的role改为"admin"
- 使用同一token再次访问/admin接口,应能成功
提示:在实际项目中,应该提供一个管理员后台或专门的API来管理用户角色,而不是直接操作数据库。
4. 项目结构重构
4.1 标准后端架构设计
将项目从单一文件重构为标准分层架构:
code复制app/
│
├── main.py # 应用入口
│
├── core/ # 核心配置和工具
│ ├── config.py # 配置文件
│ └── security.py # 安全相关工具
│
├── db/ # 数据库相关
│ ├── database.py # 数据库连接
│ └── models.py # 数据模型
│
├── schemas/ # Pydantic模型
│ └── user.py
│
├── crud/ # 数据库操作
│ └── user.py
│
├── services/ # 业务逻辑
│ └── user_service.py
│
└── routers/ # 路由定义
└── user.py
各层职责说明:
- router层:接收HTTP请求,调用service层,返回响应
- service层:处理业务逻辑,权限判断,安全处理
- crud层:封装数据库增删改查操作
- models层:定义数据库表结构
- schemas层:定义API请求/响应数据结构
4.2 重构步骤详解
4.2.1 创建项目结构
- 在项目根目录创建app文件夹
- 在app内创建core、db、schemas、crud、services、routers子目录
- 在每个目录中添加__init__.py文件(即使是空文件),使其成为Python包
4.2.2 文件迁移与重构
- 将database.py移动到app/db/database.py
- 将models.py移动到app/db/models.py
- 将auth.py重命名为security.py并移动到app/core/目录
- 更新所有import语句,确保引用路径正确
4.2.3 注册功能拆解
将注册功能从main.py中拆解到各层:
- crud层 (app/crud/user.py):
python复制from sqlalchemy.orm import Session
from app.db import models
def get_user_by_phone(db: Session, phone: str):
return db.query(models.User).filter(models.User.phone == phone).first()
def create_user(db: Session, phone: str, hashed_password: str):
user = models.User(phone=phone, password=hashed_password)
db.add(user)
db.commit()
db.refresh(user)
return user
- service层 (app/services/user_service.py):
python复制from fastapi import HTTPException
from sqlalchemy.orm import Session
from app.crud import user as user_crud
from app.core.security import hash_password
def register_user(db: Session, phone: str, password: str):
existing_user = user_crud.get_user_by_phone(db, phone)
if existing_user:
raise HTTPException(status_code=400, detail="User already exists")
hashed_password = hash_password(password)
return user_crud.create_user(db, phone, hashed_password)
- router层 (app/routers/user.py):
python复制from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from pydantic import BaseModel
from app.db.database import get_db
from app.services.user_service import register_user
router = APIRouter()
class UserCreate(BaseModel):
phone: str
password: str
@router.post("/register")
def register(user: UserCreate, db: Session = Depends(get_db)):
new_user = register_user(db, user.phone, user.password)
return {
"message": "Register success",
"user_id": new_user.id
}
- 最后在main.py中引入路由:
python复制from app.routers import user
app = FastAPI()
app.include_router(user.router)
4.3 重构后的优势
- 代码结构清晰:各层职责分明,便于维护
- 可扩展性强:新增功能只需在对应层级添加代码
- 便于测试:各层可以单独测试
- 团队协作友好:不同开发者可以并行开发不同模块
5. 常见问题与解决方案
5.1 数据库迁移问题
问题:修改模型后如何在不丢失数据的情况下更新数据库?
解决方案:
- 使用Alembic等数据库迁移工具
- 创建迁移脚本:
bash复制alembic revision --autogenerate -m "add role field"
- 应用迁移:
bash复制alembic upgrade head
5.2 权限控制扩展
问题:如何实现更复杂的RBAC权限系统?
解决方案:
- 创建角色表、权限表和关联表
- 实现权限检查中间件
- 示例模型:
python复制class Role(Base):
__tablename__ = "roles"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
class Permission(Base):
__tablename__ = "permissions"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
endpoint = Column(String)
class UserRole(Base):
__tablename__ = "user_roles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
role_id = Column(Integer, ForeignKey("roles.id"), primary_key=True)
class RolePermission(Base):
__tablename__ = "role_permissions"
role_id = Column(Integer, ForeignKey("roles.id"), primary_key=True)
permission_id = Column(Integer, ForeignKey("permissions.id"), primary_key=True)
5.3 性能优化
问题:每次权限检查都要查询数据库,如何优化?
解决方案:
- 实现JWT claims:将用户角色信息直接编码到token中
- 使用缓存(如Redis)存储用户权限信息
- 示例代码:
python复制def create_access_token(data: dict):
user = get_user(data["user_id"])
data.update({"role": user.role})
return jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)
def get_current_user(credentials = Depends(security)):
payload = verify_token(credentials.credentials)
# 不需要查数据库就能获取role
return payload
6. 项目总结与经验分享
通过这次改造,实现了基于角色的接口权限控制,并将项目重构为更合理的分层架构。几个关键经验:
- 小步快跑:每次只修改一个小功能,测试通过后再继续
- 合理分层:严格区分router、service、crud的职责
- 自动化测试:重构过程中要补充单元测试,确保功能正常
- 文档记录:每次数据库变更都要记录,便于团队协作
在实际开发中,还可以进一步优化:
- 添加Swagger文档,标注接口权限要求
- 实现更精细的权限控制系统
- 添加审计日志,记录管理员操作
- 实现用户角色管理接口
重构后的项目结构清晰、易于扩展,为后续开发打下了良好基础。特别是在多人协作开发时,这种分层架构能显著提高开发效率和代码质量。