AI 掘金头条-用户模块

用户注册、用户登录、获取和修改用户信息

image

一、用户注册功能实现

1.1.接口实现分析

接口文档:

image

示例:

image

用户注册实现思路

image

1.2.passlib 密码加密

passlib 是一个 Python 的密码哈希库,用于安全地存储和验证用户密码。它支持多种现代、安全的哈希算法(如 bcryptargon2pbkdf2_sha256 等),并提供了统一、易用的接口。在 Web 开发(尤其是 FastAPI、Flask、Django 等框架)中,绝不应该明文存储用户密码,而应使用像 passlib 这样的库对密码进行 加盐哈希(salted hash)
  • 安装
# passlib[bcrypt]==1.7.4 官⽅⻓期稳定版本
pip install passlib==1.7.4
pip install bcrypt==3.2.2
  • 使用方法

导包 → 创建密码加密上下⽂,⽤于统⼀管理密码哈希算法和策略 → 密码加密或校验

from passlib.context import CryptContext

# 创建密码加密上下⽂
pwd_context = CryptContext(
    schemes=["bcrypt"],
    deprecated="auto"
)


# 加密
def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)


# 密码校验
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


if __name__ == '__main__':
    print(get_password_hash("12345678"))

二、Token

在 Web 开发(尤其是 FastAPI、Flask 等 API 服务)中,Token(令牌) 是一种用于 身份认证(Authentication)和授权(Authorization) 的机制。它允许客户端(如前端、移动端)在登录后获得一个“凭证”,后续请求携带该凭证即可证明身份,无需反复发送用户名和密码。

  • Token:是服务器发给客户端的一段字符串,用来在后续请求中证明“你已经登录过了
  • 作用:解决 HTTP 是无状态的问题,在每次请求中"自我证明身份"

image

2.1.常见 Token 类型

1. JWT(JSON Web Token) ✅(最常用)

  • 自包含:Token 本身包含用户信息(如 user_id、角色等),无需查数据库。
  • 无状态:服务端不需要存储 session,适合分布式系统。
  • 结构:header.payload.signature(Base64 编码,用密钥签名防篡改)
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

2. Opaque Token(不透明令牌)

    • Token 只是一个随机字符串(如 UUID),服务端需查数据库或缓存(如 Redis)验证。
    • 更安全(无法被解析),但需要额外存储。

2.2.FastAPI 官方用的是哪种 Token?

在 FastAPI 官方文档和推荐实践中,使用的是 基于 JWT(JSON Web Token)的 Bearer Token 认证方案,并配合 OAuth2PasswordBearer 这个安全工具来实现。

虽然 FastAPI 本身 不强制绑定任何 Token 类型,但其 官方教程(Security 部分) 明确使用了:
  • JWT 作为 Token 的格式
  • Authorization: Bearer <token> 作为传输方式
  • OAuth2PasswordBearer 作为依赖项来自动解析和验证 Token
这是一种 轻量、无状态、适合 API 的认证方式,非常适合前后端分离、移动端等场景。

2.3.Token位置

Token 在请求中的位置:请求头

Authorization: Bearer <token>

说明:

  • Authorization: 专门用来放身份信息
  • Bearer: 表示“持有者令牌”
  • <token>:真正的身份凭证

三、封装通用成功响应格式

3.1.抽取响应结果:

在utils/common_response.py添加内容:

# 导入 FastAPI 提供的 jsonable_encoder 函数,用于将任意 Python 对象转换为 JSON 兼容的格式
from fastapi.encoders import jsonable_encoder
# 导入 FastAPI 的 JSONResponse 类,用于返回标准的 JSON 格式 HTTP 响应
from fastapi.responses import JSONResponse


def success_response(message: str = "success", data=None):
    """
    定义一个通用的成功响应函数,用于统一 API 成功时的返回格式
    :param message: 可选参数,表示操作成功的提示信息,默认为 "success"
    :param data: 可选参数,表示返回的具体业务数据,默认为 None(即无数据)
    :return:
    """
    content = {
        "code": 200,            # 业务状态码,200 表示成功(注意:这是应用层状态码,非 HTTP 状态码)
        "message": message,     # 操作结果的描述信息
        "data": data            # 实际返回的数据,可以是字典、列表、模型对象等
    }

    # 使用 jsonable_encoder 将 content 中可能包含的非 JSON 原生类型(如 Pydantic 模型、datetime 等)转换为 JSON 兼容格式
    # 然后通过 JSONResponse 返回一个标准的 JSON 响应(自动设置 Content-Type 为 application/json,HTTP 状态码默认为 200)
    return JSONResponse(content=jsonable_encoder(content))

3.2.定义数据类型

在schemas/users.py中添加内容:

from pydantic import BaseModel, Field, ConfigDict


class UserRequest(BaseModel):
    """
    用户注册请求参数
    """
    username: str
    password: str


class UserInfoResponse(BaseModel):
    """
    定义一个名为 UserInfoResponse 的 Pydantic 模型类,用于描述用户信息的响应结构
    """
    # 声明 id 字段,类型为 int,表示用户的唯一标识
    id: int
    # 声明 username 字段,类型为 str,表示用户名
    username: str

    # 配置模型的行为(Pydantic v2 语法)
    model_config = ConfigDict(
        from_attributes=True  # 允许该模型从 ORM 对象(如 SQLAlchemy 模型实例)中自动提取属性值(不仅限于字典) 取值然后赋值给id、username字段
    )


class UserAuthResponse(BaseModel):
    """
    定义 Pydantic 模型类 UserAuthResponse,用于描述用户登录成功后的响应结构
    """
    # 声明 token 字段,类型为 str,通常为 JWT 或其他认证令牌
    token: str

    # 声明 user_info 字段,类型为嵌套的 UserInfoResponse 模型
    # Field(...) 表示该字段是必需的(required),不可省略
    # description 参数用于生成 OpenAPI 文档时提供字段说明
    user_info: UserInfoResponse = Field(..., description="用户信息")

    # 配置该模型的行为
    model_config = ConfigDict(
        populate_by_name=True,  # 允许在反序列化(如解析请求体)时,使用字段名(而非 alias)来匹配数据;
        from_attributes=True  # 同样允许从 ORM 模型对象中提取属性,便于将数据库模型直接转换为此响应模型
    )

3.3.调用函数响应结果

在注册接口中调用响应结果

    # 调用 UserInfoResponse 的类方法 .model_validate(),将 new_user(ORM 对象)转换为 UserInfoResponse 类型的 Pydantic 模型实例。
    response_data = UserAuthResponse(token=token, user_info=UserInfoResponse.model_validate(new_user))
    return success_response(message="注册成功", data=response_data)

四、全局异常处理器

全局异常处理器(Global Exception Handler)是注册在 FastAPI 应用级别的异常处理函数,用于捕获业务层、数据库层以及系统层抛出的异常,并以统一的响应格式返回给前端。

异常:

  • SQL 错误
  • 外键关联失败
  • 数据库连接异常
  • 提交事务失败
  • ......

4.1.实现步骤:

  • step1:定义异常处理器(函数) 
  • step2:全局注册异常处理器
app.add_exception_handler()

4.2.注册顺序:

  1. 业务异常
  2. 数据约束异常(子类)
  3. 数据库异常(父类)
  4. 所有异常

4.3.代码实现

  • step1:定义异常处理器(函数) 

异常处理器定义,在common_exception.py中定义异常处理函数:

import traceback  # 导入 traceback 模块,用于获取完整的异常堆栈信息(调试用)

from fastapi import HTTPException, Request  # 从 fastapi 导入 HTTPException(FastAPI 自定义的 HTTP 错误)和 Request(请求对象)
from fastapi.responses import JSONResponse  # 从 fastapi.responses 导入 JSONResponse,用于返回结构化的 JSON 响应
from sqlalchemy.exc import IntegrityError, SQLAlchemyError  # 从 sqlalchemy.exc 导入数据库相关的异常类型
from starlette import status  # 从 starlette 导入 HTTP 状态码常量(如 400、500 等)

# DEBUG_MODE 控制是否在响应中暴露详细的错误信息
# 开发模式(True):返回完整错误堆栈,便于调试
# 生产模式(False):只返回通用提示,避免泄露敏感信息
DEBUG_MODE = True  # 默认为开发模式


async def http_exception_handler(request: Request, exc: HTTPException):
    """
    处理由业务逻辑主动抛出的 HTTPException(例如:raise HTTPException(status_code=404, detail="Not found"))
    :param request: 当前 HTTP 请求对象(可用于记录路径等信息)
    :param exc: 捕获到的 HTTPException 实例
    :return: 返回标准化的 JSON 响应
    """
    # HTTPException 是开发者主动抛出的“预期错误”,所以直接使用其状态码和描述
    return JSONResponse(
        status_code=exc.status_code,  # 使用异常自带的状态码(如 400、401、404 等)
        content={
            "code": exc.status_code,      # 业务约定的错误码(通常与 HTTP 状态码一致)
            "message": exc.detail,        # 错误描述(由开发者传入)
            "data": None                  # 无数据返回(因为是错误)
        }
    )


async def integrity_error_handler(request: Request, exc: IntegrityError):
    """
    处理数据库完整性约束错误(例如:唯一索引冲突、外键约束失败等)
    :param request: 当前请求对象
    :param exc: 捕获到的 IntegrityError(SQLAlchemy 抛出)
    :return: 返回友好的错误提示 + 可选的详细信息
    """
    # 获取底层数据库驱动抛出的原始错误信息(exc.orig 是真正的 DB 异常)
    error_msg = str(exc.orig)

    # 根据错误信息判断具体原因,返回用户友好的提示
    if "username_UNIQUE" in error_msg or "Duplicate entry" in error_msg:
        detail = "用户名已存在"  # 唯一索引冲突(比如用户名重复)
    elif "FOREIGN KEY" in error_msg:
        detail = "关联数据不存在"  # 关联数据不存在
    else:
        detail = "数据库约束冲突,请检查输入"

    # 如果是开发模式,附加详细错误信息(用于调试)
    error_data = None
    if DEBUG_MODE:
        error_data = {
            "error_type": "IntegrityError",
            "error_detail": error_msg,       # 原始数据库错误
            "path": str(request.url)         # 触发错误的请求路径
        }

    # 返回 400 Bad Request(因为是客户端输入导致的问题)
    return JSONResponse(
        status_code=status.HTTP_400_BAD_REQUEST,
        content={
            "code": 400,
            "message": detail,
            "data": error_data
        }
    )


async def sqlalchemy_error_handler(request: Request, exc: SQLAlchemyError):
    """
    处理所有其他 SQLAlchemy 相关的数据库错误(非完整性约束)
    :param request: 当前请求
    :param exc: SQLAlchemyError 或其子类(如连接失败、语法错误等)
    :return: 返回 500 错误 + 可选调试信息
    """
    error_data = None

    if DEBUG_MODE:
        error_data = {
            "error_type": type(exc).__name__,   # 异常类名(如 OperationalError)
            "error_detail": str(exc),           # 异常消息
            "traceback": traceback.format_exc(),# 完整堆栈(非常有用!)
            "path": str(request.url)
        }

    # 数据库内部错误,属于服务器问题,返回 500
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "code": 500,
            "message": "数据库操作失败,请稍后重试",
            "data": error_data
        }
    )


async def general_exception_handler(request: Request, exc: Exception):
    """
    兜底异常处理器:捕获所有未被前面处理器处理的异常(包括程序 bug)
    注意:必须注册在最后,否则会“吞掉”更具体的异常
    :param request: 当前请求
    :param exc: 任意未捕获的异常
    :return: 返回 500 + 调试信息(仅开发模式)
    """
    error_data = None

    if DEBUG_MODE:
        error_data = {
            "error_type": type(exc).__name__,
            "error_detail": str(exc),
            "traceback": traceback.format_exc(),
            "path": str(request.url)
        }

    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "code": 500,
            "message": "服务器内部错误,请稍后重试",
            "data": error_data
        }
    )

异常处理器函数封装:在exception_handlers.py中编写内容:

from fastapi import HTTPException
from sqlalchemy.exc import IntegrityError, SQLAlchemyError

from utils.common_exception import http_exception_handler, integrity_error_handler, sqlalchemy_error_handler, \
    general_exception_handler


def register_exception_handlers(app):
    """
    将自定义的异常处理器注册到 FastAPI 应用实例上
    注意:注册顺序非常重要!
    - 子类异常必须在父类之前注册(否则会被父类 handler 先捕获)
    - 具体异常在前,通用异常(如 Exception)在最后
    :param app: FastAPI 应用实例
    :return: None
    """
    # 1. 处理 HTTPException(业务主动抛出的错误)
    app.add_exception_handler(HTTPException, http_exception_handler)

    # 2. 处理数据库完整性错误(IntegrityError 是 SQLAlchemyError 的子类)
    app.add_exception_handler(IntegrityError, integrity_error_handler)

    # 3. 处理其他数据库错误(SQLAlchemyError 是更宽泛的父类)
    app.add_exception_handler(SQLAlchemyError, sqlalchemy_error_handler)

    # 4. 最后兜底:捕获所有剩余异常(Exception 是所有异常的基类)
    # ⚠️ 如果放在前面,会提前捕获所有异常,导致前面的 handler 失效!
    app.add_exception_handler(Exception, general_exception_handler)
  • step2:全局注册异常处理器,在main.py中注册
from fastapi import FastAPI
from routers import news, users
from fastapi.middleware.cors import CORSMiddleware

from utils.exception_handlers import register_exception_handlers

app = FastAPI()

# 注册异常处理器
register_exception_handlers(app)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许的源,开发阶段允许所有的源,生产环境需要制定源, 👈 明确指定前端地址例如: ["http://localhost:3000"]
    allow_credentials=True,  # 允许携带cookie
    allow_methods=["*"],  # 允许的请求方法
    allow_headers=["*"]  # 允许的请求头
)


@app.get("/")
async def root():
    return {"message": "Hello World"}


# 挂载路由/注册路由
app.include_router(news.router)
app.include_router(users.router)

四、用户登录

接口文档:

image

示例:

image

 实现思路:

image

五、HTTP 头部参数

请求头(HTTP Header)主要包含身份信息、请求控制信息、数据格式说明和客户端环境信息。

在 FastAPI 中,Header 是一个用于从 HTTP 请求头(Request Headers)中提取参数的依赖项(Dependency)。它属于 FastAPI 的“路径操作函数参数依赖”机制的一部分,与 Query、Path、Body 等类似,但专门用于处理请求头字段。

5.1.✅ 基本用法

from fastapi import FastAPI, Header
from typing import Optional

app = FastAPI()

@app.get("/items/")
async def read_items(user_agent: Optional[str] = Header(None)):
    return {"User-Agent": user_agent}
注意:HTTP 头字段名是不区分大小写的,但在 Python 中变量名必须合法。FastAPI 会自动将下划线 _ 转换为连字符 -(反之亦然)来匹配 HTTP 头。
例如:
  • user_agent → 对应 HTTP 头中的 User-Agent
  • x_token → 对应 X-Token

5.2.✅ 多值请求头(重复头)

某些 HTTP 头可以出现多次(如 X-Token: abcX-Token: xyz),FastAPI 支持接收为列表:
@app.get("/items/")
async def read_items(x_token: Optional[list[str]] = Header(None)):
    return {"X-Token values": x_token}

如果客户端发送:

GET /items/ HTTP/1.1
X-Token: secret1
X-Token: secret2

则 x_token 将是 ["secret1", "secret2"]

5.3.✅ 必填请求头

若要求某个头必须存在,可设置默认值为 ...(Ellipsis)或使用 Pydantic 的 Field:
@app.get("/items/")
async def read_items(x_token: str = Header(...)):
    return {"x-token": x_token}

或者带描述:

from fastapi import Header
from pydantic import Field

@app.get("/items/")
async def read_items(
    x_token: str = Header(..., alias="X-Token", description="认证令牌")
):
    return {"x-token": x_token}
实际上,在 FastAPI 中通常不需要显式写 alias="X-Token",因为 x_token 会自动映射到 X-Token

⚠️ 注意事项

  1. 命名转换规则
    • Python 参数名使用下划线(如 user_agent
    • 自动映射到标准 HTTP 头格式(如 User-Agent
    • 所有头字段名在 HTTP 中不区分大小写
  2. 安全敏感头(如 Authorization)通常由中间件或依赖项统一处理,而非直接在路径函数中读取。
  3. 不要手动解析原始 headers:FastAPI 已封装好,推荐使用 Header() 依赖。

六、获取用户信息

查询接口文档:

image

示例:

image

实现思路:

image

七、修改用户信息

接口文档:

image

示例:

image

 实现思路:

image

八、修改密码

接口文档:

image

实例:

image

修改密码实现思路:

image

 

posted @ 2026-01-28 14:59  酒剑仙*  阅读(2)  评论(0)    收藏  举报