AI 掘金头条-用户模块
用户注册、用户登录、获取和修改用户信息

一、用户注册功能实现
1.1.接口实现分析
接口文档:

示例:

用户注册实现思路

1.2.passlib 密码加密
passlib 是一个 Python 的密码哈希库,用于安全地存储和验证用户密码。它支持多种现代、安全的哈希算法(如 bcrypt、argon2、pbkdf2_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 是无状态的问题,在每次请求中"自我证明身份"

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 这个安全工具来实现。
- JWT 作为 Token 的格式
Authorization: Bearer <token>作为传输方式OAuth2PasswordBearer作为依赖项来自动解析和验证 Token
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.注册顺序:
- 业务异常
- 数据约束异常(子类)
- 数据库异常(父类)
- 所有异常
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)
四、用户登录
接口文档:

示例:

实现思路:

五、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-Agentx_token→ 对应X-Token
5.2.✅ 多值请求头(重复头)
X-Token: abc, X-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。
⚠️ 注意事项
-
命名转换规则:
- Python 参数名使用下划线(如
user_agent) - 自动映射到标准 HTTP 头格式(如
User-Agent) - 所有头字段名在 HTTP 中不区分大小写
- Python 参数名使用下划线(如
-
安全敏感头(如
Authorization)通常由中间件或依赖项统一处理,而非直接在路径函数中读取。 -
不要手动解析原始 headers:FastAPI 已封装好,推荐使用
Header()依赖。
六、获取用户信息
查询接口文档:

示例:

实现思路:

七、修改用户信息
接口文档:

示例:

实现思路:

八、修改密码
接口文档:

实例:

修改密码实现思路:


浙公网安备 33010602011771号