企业级FastAPI后端模板搭建(六)加密用户密码

查看sys_user表(如下图),可以发现密码是明文,安全性不高,所以需要进行加密。

封装数据库CURD

新建 core/crud.py 文件:

python 复制代码
from typing import Any, Generic, NewType, TypeVar
from tortoise.models import Model
from pydantic import BaseModel
from tortoise.expressions import Q

ModelType = TypeVar("ModelType", bound=Model)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
Total = NewType("Total", int)

class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, model: type[ModelType]):
        self.model = model

    async def get(self, id: int) -> ModelType:
        """根据ID获取单个记录"""
        return await self.model.get(id=id)
    
    async def list(self, page: int, page_size: int, search: Q = Q(), order: list | None = None) -> tuple[Total, list[ModelType]]:
        """分页查询记录列表"""
        query = self.model.filter(search)
        if order is None:
            order = []
        total = await query.count()
        items = await query.offset((page - 1) * page_size).limit(page_size).order_by(*order)
        return total, items
    
    async def create(self, obj_in: CreateSchemaType) -> ModelType:
        """创建新记录"""
        if isinstance(obj_in, dict):
            obj_dict = obj_in
        else:
            obj_dict = obj_in.model_dump()
        obj = self.model(**obj_dict)
        await obj.save()
        return obj
    
    async def update(self, id: int, obj_in: UpdateSchemaType | dict[str, Any]) -> ModelType:
        """更新指定记录"""
        if isinstance(obj_in, dict):
            obj_dict = obj_in
        else:
            obj_dict = obj_in.model_dump(exclude_unset=True, exclude={"id"})
        obj = await self.get(id=id)
        obj = obj.update_from_dict(obj_dict)
        await obj.save()
        return obj
    
    async def remove(self, id: int) -> None:
        """删除单个记录"""
        obj = await self.get(id=id)
        await obj.delete()

    async def bulk_remove(self, ids: str) -> None:
        """批量删除记录"""
        id_list = ids.split(",")
        await self.model.filter(id__in=id_list).delete()

封装密码加密

安装依赖

首先安装 passlib 库:

shell 复制代码
pip install passlib[bcrypt] setuptools argon2-cffi

实现密码工具

新建 utils/crud.py 文件:

python 复制代码
from passlib import pwd
from passlib.context import CryptContext

# 使用 Argon2 算法创建密码上下文
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")

def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证明文密码与哈希密码是否匹配"""
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    """生成密码哈希值"""
    return pwd_context.hash(password)

def generate_password() -> str:
    """生成随机密码"""
    return pwd.genword()

创建UserRepository

安装依赖

shell 复制代码
pip install pydantic[email]

创建Schemas

新建 schemas/users.py 文件:

python 复制代码
from datetime import datetime
from pydantic import BaseModel, EmailStr, Field

class BaseUser(BaseModel):
  id: int
  account: str | None = None
  username: str | None = None
  email: EmailStr | None = None
  is_active: bool | None = True
  is_superuser: bool | None = False
  created_at: datetime | None
  updated_at: datetime | None
  last_login: datetime | None
  roles: list | None = []

class UserCreate(BaseModel):
  account: str = Field(example="admin")
  username: str = Field('', example="Admin1")
  gender: int | None = Field(0, description="性别")
  password: str = Field(
    default="admin1234",
    example="AdminPass123",
    min_length=8,
    description="密码(至少8位,包含字母和数字)",
  )
  avatar: str | None = Field('', description="用户头像")
  mobile: str = Field(None, description="联系方式")
  email: EmailStr = Field('', example="admin@qq.com")
  status: int | None = Field(1, description="状态")
  is_active: bool | None = True
  is_superuser: bool | None = False
  dept_id: int | None = Field(0, description="部门ID")
  create_by: int | None = Field(None, description="创建人")
  update_by: int | None = Field(None, description="修改人")

class UserUpdate(BaseModel):
  id: int
  account: str
  username: str = Field('', example="Admin1")
  gender: int | None = Field(0, description="性别")
  avatar: str | None = Field('', description="用户头像")
  mobile: str | None = Field('', description="联系方式")
  email: str | None = Field('', example="admin@qq.com")
  status: int | None = Field(1, description="状态")
  is_active: bool | None = True
  is_superuser: bool | None = False
  role_ids: list[int] | None = []
  dept_id: int | None = 0
  update_by: int | None = Field(None, description="修改人")

创建Repository

新建 repositories/user.py 文件:

python 复制代码
from core.crud import CRUDBase
from models.admin import User
from schemas.users import UserCreate, UserUpdate
from utils.password import get_password_hash

class UserRepository(CRUDBase[User, UserCreate, UserUpdate]):
  def __init__(self):
    super().__init__(model=User)

  async def create_user(self, obj_in: UserCreate) -> User:
    obj_in.password = get_password_hash(password=obj_in.password)
    obj = await self.create(obj_in)
    return obj

user_repository = UserRepository()

修改初始化文件

修改core/insert_data.py文件:

python 复制代码
from repositories.user import UserCreate, user_repository

...
async def init_superuser():
  logger.info("🔧 开始初始化超级管理员用户...")
  user = await user_repository.model.exists()
  if not user:
    await user_repository.create_user(
      UserCreate(
        account="admin",
        username="超级管理员",
        email="admin@admin.com",
        password="abcd1234",
        dept_id=1,
        is_active=True,
        is_superuser=True,
      )
    )
    logger.info("✅ 超级管理员用户创建成功 - 用户名: admin")
  else:
    logger.info("ℹ️ 超级管理员用户已存在,跳过创建")

先清空sys_user表,之后再运行项目,运行成功之后可以看到密码已经加密了。

相关推荐
闲猫2 小时前
Python FastAPI + SQLAlchemy 入门教程:从零搭建你的第一个 Web 应用
前端·python·fastapi
海天鹰11 小时前
WIN10修改窗口标题栏失去焦点背景色
windows
梦帮科技12 小时前
GRAVIS v5.5:向硬核桌面端进化与云端多节点容灾部署实践
windows·架构·rust·自动化·区块链·智能合约·数字货币
sukioe13 小时前
从 0 到 1 构建城市公共设施智能报修与派单系统:FastAPI + Vue3 + RabbitMQ + Redis Geo + AI 工作流实践
redis·rabbitmq·fastapi
L-影13 小时前
FastAPI 请求头与 Cookie:Web 世界的“身份证”与“通行证”
前端·fastapi
tianyazhichiC13 小时前
【VLC player 下载地址】
windows
L-影13 小时前
FastAPI CORS 跨域:Web 世界的“小区门禁”与“访客通行证”
前端·javascript·fastapi
小呀小叮当~14 小时前
OpenAI Codex 桌面版(Windows / macOS)体验分享:AI 编程效率再次提升
人工智能·windows·macos
Starry-sky(jing)15 小时前
Win11 装 ESP32 Arduino 库卡 3 小时?我把官方 3.3.10 全平台 522MB 搬到了蓝奏云,解压即用
运维·windows·esp32·arduino·离线安装