1、用户模块
本模块涉及三个核心功能的开发

1、用户注册
1️⃣ 接口文档
- 接口地址 :
POST /api/user/register - 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
- 请求示例:
json
{
"username": "example_user",
"password": "example_password"
}
- 响应示例:
json
{
"code": 200,
"message": "注册成功",
"data": {
"token": "用户访问令牌",
"userInfo": {
"id": 1,
"username": "example_user",
"bio": "这个人很懒,什么都没留下",
"avatar": "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
}
}
}
2️⃣ 基础路由实现
先创建一个routers/user.py文件
python
from fastapi import APIRouter
router = APIRouter(prefix="/api/user", tags=["users"])
@router.post("/register")
async def register():
return {
"code": 200,
"message": "注册成功",
"data": {
"token": "用户访问令牌",
"userInfo": {
"id": 1,
"username": "example_user",
"bio": "这个人很懒,什么都没留下",
"avatar": "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
}
}
}
然后再main.py中进行挂载
python
from routers import news,users
app.include_router(users.router)
接着创建一个为用户注册进行类型校验的模块
python
# 该模块为用户注册做类型校验
from pydantic import BaseModel
class UserRequest(BaseModel):
username: str
password: str
python
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from schemas.users import UserRequest
from config.db_config import get_db
router = APIRouter(prefix="/api/user", tags=["users"])
@router.post("/register")
async def register(user_data: UserRequest,db: AsyncSession= Depends(get_db)): # 用户信息 和 db
return {
"code": 200,
"message": "注册成功",
"data": {
"token": "用户访问令牌",
"userInfo": {
"id": 1,
"username": user_data.username,
"bio": "这个人很懒,什么都没留下",
"avatar": "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
}
}
}


3️⃣ 用户注册实现

先准备用户表和Token表模型类
python
from datetime import datetime
from typing import Optional
from sqlalchemy import Index, Integer, String, Enum, DateTime, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
"""
用户信息表ORM模型
"""
__tablename__ = 'user'
# 创建索引
__table_args__ = (
Index('username_UNIQUE', 'username'),
Index('phone_UNIQUE', 'phone'),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment="用户ID")
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="用户名")
password: Mapped[str] = mapped_column(String(255), nullable=False, comment="密码(加密存储)")
nickname: Mapped[Optional[str]] = mapped_column(String(50), comment="昵称")
avatar: Mapped[Optional[str]] = mapped_column(String(255), comment="头像URL",
default='https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg')
gender: Mapped[Optional[str]] = mapped_column(Enum('male', 'female', 'unknown'), comment="性别", default='unknown')
bio: Mapped[Optional[str]] = mapped_column(String(500), comment="个人简介", default='这个人很懒, 什么都没留下')
phone: Mapped[Optional[str]] = mapped_column(String(20), unique=True, comment="手机号")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now(), comment="创建时间")
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now(), onupdate=datetime.now(),
comment="更新时间")
def __repr__(self):
return f"<User(id={self.id}, username='{self.username}', nickname='{self.nickname}')>"
class UserToken(Base):
"""
用户令牌表ORM模型
"""
__tablename__ = 'user_token'
# 创建索引
__table_args__ = (
Index('token_UNIQUE', 'token'),
Index('fk_user_token_user_idx', 'user_id'),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment="令牌ID")
user_id: Mapped[int] = mapped_column(Integer, ForeignKey(User.id), nullable=False, comment="用户ID")
token: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, comment="令牌值")
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, comment="过期时间")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now(), comment="创建时间")
def __repr__(self):
return f"<UserToken(id={self.id}, user_id={self.user_id}, token='{self.token}')>"
这里的Optional表示可选的,说明该字段可以为空

封装crud代码
python
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from models.users import User
from schemas.users import UserRequest
# 根据用户名查询数据库
async def get_user_by_username(db: AsyncSession, username: str):
query = select(User).where(User.username == username)
result = await db.execute(query)
return result.scalar_one_or_none()
接下来需要写创建用户的方法
创建用户的话,涉及到密码的操作,在生产环境中,密码都是加密处理的,因为这里使用Python的第三方模块来进行加密--->passlib 密码加密

powershell
# passlib[bcrypt]==1.7.4 官⽅⻓期稳定版本
pip install "passlib[bcrypt]==1.7.4"
passlib 密码加密流程
1、安装依赖
2、创建加密上下文
3、加密或校验密码
1️⃣ 安装依赖
- 作用:安装指定版本的 passlib 库,同时附带 bcrypt 算法支持,是后续密码加密的基础。
2️⃣ 创建加密上下文
- 作用:初始化一个密码处理的核心对象,指定使用 bcrypt 算法,自动处理废弃算法,统一管理后续的加密和校验操作。
3️⃣ 加密或校验
- 加密:
pwd_context.hash(password)
- 作用:把用户的明文密码,转换成不可逆的 bcrypt 哈希字符串。
- 校验:
pwd_context.verify(plain_password, hashed_password)
- 作用:对比用户输入的明文密码,和数据库里存的哈希密码是否匹配,返回 True/False。
plain
pip install "passlib[bcrypt]==1.7.4"
plain
pwd_context = CryptContext(
schemes=["bcrypt"],
deprecated="auto")
编写密码加密模块
python
from passlib.context import CryptContext
# 创建密码上下文对象,指定使用bcrypt算法
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# 密码加密
def get_hash_password(password: str):
return pwd_context.hash(password)
实现curd创建用户的方法
python
# 创建用户
async def create_user(db:AsyncSession,user_data: UserRequest):
# 先密码加密处理 -> add用户
hashed_password = security.get_hash_password(user_data.password)
user = User(username=user_data.username, password=hashed_password)
db.add(user)
await db.commit()
await db.refresh(user) # 从数据库中读回最新的user数据,确保拿到的数据就是写入到数据库的数据
return user
接着在路由模块中调用这些方法
python
@router.post("/register")
async def register(user_data: UserRequest,db: AsyncSession= Depends(get_db)): # 用户信息 和 db
# 注册逻辑:验证用户是否存在->创建用户->生成Token->响应结果
existing_user = await users.get_user_by_username(db, user_data.username)
# 验证用户是否存在
if existing_user:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="用户已存在")
user = await users.create_user(db, user_data)
return {
"code": 200,
"message": "注册成功",
"data": {
"token": "用户访问令牌",
"userInfo": {
"id": user.id,
"username": user.username,
"bio": user.bio,
"avatar": user.avatar
}
}
}


测试报错了

遇到的问题是 bcrypt 4.0+ 版本与 passlib 的兼容性问题。通过安装 bcrypt==3.2.2 这个稳定版本,成功解决了密码加密时的错误。
这里我们需要降级bcrypt依赖的版本
powershell
pip install bcrypt==3.2.2
然后再次进行测试,现在就可以成功了
现在bcrypt和 passlib的版本如下:

这个也可以尝试其他bcrypt的版本试试,因为现在最新版本(我安装的bcrypt版本是5.0.0)的使用起来和 passlib不兼容


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

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

- Authorization:专门用来放身份信息
- Bearer:表示"持有者令牌"
- :真正的身份凭证
python
# 生成Token
async def create_token(db: AsyncSession, user_id: int):
# 生成Token + 设置过期时间->查询数据库当前用户是否有Token->有:更新;没有:添加
token = uuid.uuid4()
# timedelta(days=7,hours=2,minutes=30,seconds=10)
expires_at = datetime.now() + timedelta(days=7)
query = select(UserToken).where(UserToken.user_id == user_id)
result = await db.execute(query)
user_token = result.scalar_one_or_none()
if user_token:
user_token.token = token
user_token.expires_at = expires_at
else:
user_token = UserToken(user_id=user_id, token=token, expires_at=expires_at)
db.add(user_token)
await db.commit()
return token
python
@router.post("/register")
async def register(user_data: UserRequest, db: AsyncSession = Depends(get_db)): # 用户信息 和 db
# 注册逻辑:验证用户是否存在->创建用户->生成Token->响应结果
existing_user = await users.get_user_by_username(db, user_data.username)
# 验证用户是否存在
if existing_user:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="用户已存在")
user = await users.create_user(db, user_data)
token = await users.create_token(db, user.id)
return {
"code": 200,
"message": "注册成功",
"data": {
"token": token,
"userInfo": {
"id": user.id,
"username": user.username,
"bio": user.bio,
"avatar": user.avatar
}
}
}
接着在前端进行测试


5️⃣ 封装通用成功响应格式

为什么一定要封装一个统一的响应类
- 自动校验数据,防止返回错误内容
字典是 "随便写" 的:
- 少写字段、写错名字、返回错误类型,代码不会报错
- 前端拿到数据会直接崩溃
Pydantic 模型:
- 你必须按定义的字段返回
- 少传、传错、类型不对,接口直接报错,提前发现 bug
- 自动生成接口文档,前端直接用
FastAPI 会自动生成 Swagger 文档:
- 封装了模型 → 文档里能看到每个字段的含义、类型、示例
- 前端不用问你,直接看文档开发
直接返回字典 → 文档里看不到任何结构,全靠沟通
- 代码更规范、好维护、支持复用
- 所有接口返回格式统一
- 字段名写错会直接标红报错(字典不会)
- 多个接口可以复用同一个响应模型
- 后期改字段,只改一处,不用到处找字典
1、抽取响应结果
把这个响应数据注释掉,然后抽取一个公共的响应类
python
# return {
# "code": 200,
# "message": "注册成功",
# "data": {
# "token": token,
# "userInfo": {
# "id": user.id,
# "username": user.username,
# "bio": user.bio,
# "avatar": user.avatar
# }
# }
# }
python
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
def success_response(message: str = "success", data=None):
content = {
"code": 200,
"message": message,
"data": data
}
# 目标:把任何的FastAPI、Pydantic、ORM对象都要正常响应->code、message、data
return JSONResponse(jsonable_encoder(content))
2、定义数据类型
python
# user_info 对应的响应数据类:基础类 + Info类(id、用户名)
class UserInfoBase(BaseModel):
"""
⽤户信息基础数据模型
"""
nickname: Optional[str] = Field(None, max_length=50, description="昵称")
avatar: Optional[str] = Field(None, max_length=255, description="头像URL")
gender: Optional[str] = Field(None, max_length=10, description="性别")
bio: Optional[str] = Field(None, max_length=500, description="个⼈简介")
class UserInfoResponse(UserInfoBase):
id: int
username: str
# 模型类配置
model_config = ConfigDict(
populate_by_name=True, # alias/字段名兼容
from_attributes=True # 允许从ORM对象属性中取值
)
# data 数据类型
class UserAuthResponse(BaseModel):
token: str
user_info: UserInfoResponse = Field(..., alias="userInfo")
# 模型类配置
model_config = ConfigDict(
populate_by_name=True, # alias/字段名兼容
from_attributes=True # 允许从ORM对象属性中取值
)
3、调用函数响应结果
python
@router.post("/register")
async def register(user_data: UserRequest, db: AsyncSession = Depends(get_db)): # 用户信息 和 db
# 注册逻辑:验证用户是否存在->创建用户->生成Token->响应结果
existing_user = await users.get_user_by_username(db, user_data.username)
# 验证用户是否存在
if existing_user:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="用户已存在")
user = await users.create_user(db, user_data)
token = await users.create_token(db, user.id)
# return {
# "code": 200,
# "message": "注册成功",
# "data": {
# "token": token,
# "userInfo": {
# "id": user.id,
# "username": user.username,
# "bio": user.bio,
# "avatar": user.avatar
# }
# }
# }
response_data = UserAuthResponse(token=token, user_info=UserInfoResponse.model_validate(user))
return success_response(data=response_data)
进行测试发现报错


这个错误的原因是 类型不匹配:
- 问题根源:在 crud/users.py 的第 31 行,create_token 函数生成的是一个 UUID 对象(uuid.uuid4())
- 期望类型:但在 schemas/users.py 的第 35 行,UserAuthResponse 模型中的 token 字段定义为 str 类型
- 验证失败:当你尝试将 UUID 对象赋值给需要字符串的字段时,Pydantic 验证器就会报错
只需将 uuid.uuid4() 改为 str(uuid.uuid4()) 即可解决问题。
python
# 生成Token
async def create_token(db: AsyncSession, user_id: int):
# 生成Token + 设置过期时间->查询数据库当前用户是否有Token->有:更新;没有:添加
token = str(uuid.uuid4())
# timedelta(days=7,hours=2,minutes=30,seconds=10)
expires_at = datetime.now() + timedelta(days=7)
query = select(UserToken).where(UserToken.user_id == user_id)
result = await db.execute(query)
user_token = result.scalar_one_or_none()
if user_token:
user_token.token = token
user_token.expires_at = expires_at
else:
user_token = UserToken(user_id=user_id, token=token, expires_at=expires_at)
db.add(user_token)
await db.commit()
return token
6️⃣ 全局异常处理器
全局异常处理器(Global Exception Handler)是注册在 FastAPI 应用级别的异常处理函数 ,用于捕获业务层、数据库层以及系统层抛出的异常,并以统一的响应格式返回给前端。
异常:
- SQL 错误
- 外键关联失败
- 数据库连接异常
- 提交事务失败
- ...
FastAPI中实现全局异常处理器步骤
step1:定义异常处理器(函数)
step2 :全局注册异常处理器,app.add **exception **handler()
注册的顺序:
- 业务异常
- 数据约束异常(子类)
- 数据库异常(父类)
- 所有异常
python
import traceback
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from starlette import status
# 开发模式:返回详细错误信息
# 生产模式:返回简化错误信息
DEBUG_MODE = True # 教学项目保持开启
async def http_exception_handler(request: Request, exc: HTTPException):
"""
处理 HTTPException 异常
"""
# HTTPException 通常是业务逻辑主动抛出的, data 保持 None
return JSONResponse(
status_code=exc.status_code,
content={
"code": exc.status_code,
"message": exc.detail,
"data": None
}
)
async def integrity_error_handler(request: Request, exc: IntegrityError):
"""
处理数据库完整性约束错误
"""
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)
}
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 数据库错误
"""
# 开发模式下返回详细错误信息
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
}
)
async def general_exception_handler(request: Request, exc: Exception):
"""
处理所有未捕获的异常
"""
# 开发模式下返回详细错误信息
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
}
)
定义一个函数,一次性全部注册上面这四个异常处理器
python
from fastapi import HTTPException
from pymysql import IntegrityError
from sqlalchemy.exc import SQLAlchemyError
from utils.exception import http_exception_handler, integrity_error_handler, sqlalchemy_error_handler, \
general_exception_handler
def register_exception_handler(app):
"""
注册全局异常处理,子类在前,父类在后;具体的在前面,抽象在后
:param app:
:return:
"""
app.add_exception_handler(HTTPException, http_exception_handler)# 业务层报错异常处理
app.add_exception_handler(IntegrityError, integrity_error_handler) #数据完整性约束异常处理
app.add_exception_handler(SQLAlchemyError,sqlalchemy_error_handler) # 数据库异常处理
app.add_exception_handler(Exception, general_exception_handler) # 其他异常处理,兜底的方案
在main.py里面调用实现注册
python
from utils.exception_handler import register_exception_handler
app = FastAPI()
register_exception_handler(app)
2、用户登录
1️⃣ 接口文档
- 接口地址 :
POST /api/user/login - 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 是 | 用户名 |
| password | string | 是 | 密码 |
- 请求示例:
json
{
"username": "example_user",
"password": "example_password"
}
- 响应示例:
json
{
"code": 200,
"message": "登录成功",
"data": {
"token": "用户访问令牌",
"userInfo": {
"id": 1,
"username": "example_user",
"nickname": null,
"avatar": "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg",
"bio": "这个人很懒,什么都没留下"
}
}
}
2️⃣ 功能实现

实现crud方法
python
# 验证登录
async def authenticate_user(db: AsyncSession, username: str, password: str):
user = await get_user_by_username(db, username)
if not user:
return None
if not security.verify_password(password, user.password):
return None
return user
实现验证密码
python
# 验证密码:verify返回值是布尔值
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
路由调用
python
@router.post("/login")
async def login(user_data: UserRequest, db: AsyncSession = Depends(get_db)):
# 登录逻辑:验证用户是否存在->验证密码--->生成Token->响应结果
user = await users.authenticate_user(db, user_data.username, user_data.password)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误")
token = await users.create_token(db, user.id)
response_data = UserAuthResponse(token=token, user_info=UserInfoResponse.model_validate(user))
return success_response(message="登录成功", data=response_data)
3、获取用户信息
1️⃣ 接口文档
- 接口地址 :
GET /api/user/info - 请求头: 需要认证
- 响应示例:
json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"username": "example_user",
"nickname": null,
"avatar": "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg",
"gender": "unknown",
"bio": "这个人很懒,什么都没留下"
}
}
2️⃣ 功能实现

**HTTP 头部参数:**请求头(HTTP Header)主要包含身份信息、请求控制信息、数据格式说明和客户端环境信息。Header 用来从 HTTP 请求头中获取数据,是 FastAPI 提供的参数依赖工具
这里因为实现获取个人信息/修改信息/密码呀都需要登录,因此抽取一个工具模块
先封装crud代码
python
# 根据Token查询用户:验证Token->查询用户
async def get_user_by_token(db: AsyncSession, token: str):
query = select(UserToken).where(UserToken.token == token)
result = await db.execute(query)
db_token = result.scalar_one_or_none()
# 验证Token是否过期以及Token是否存在,如果过期/不存在则返回None
if not db_token or db_token.expires_at < datetime.now():
return None
query = select(User).where(User.id == db_token.user_id)
result = await db.execute(query)
return result.scalar_one_or_none()
封装工具函数->验证登录的函数
python
from fastapi import Header, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status
from config.db_config import get_db
from crud import users
# 整合 根据Token查询用户,返回用户
async def get_current_user(
authorization: str = Header(...,alias="Authorization"),
db: AsyncSession = Depends(get_db)
):
# Token格式:Bearer xxxxxxxxxxxxx
# 获取Token的值:方法一
# token = authorization.split(" ")[1]
# 获取Token的值:方法二
token = authorization.replace("Bearer ","")
user = await users.get_user_by_token(db,token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的令牌或已经过期的令牌")
return user
路由调用
python
# 查Token查用户->封装crud->功能整合成一个工具函数->路由导入使用:依赖注入
@router.get("/info")
async def get_user_info(user: User = Depends(get_current_user)):
return success_response(message="获取用户信息成功", data=UserInfoResponse.model_validate(user))
4、修改用户信息
1️⃣ 接口文档
- 接口地址 :
PUT /api/user/update - 请求头: 需要认证
- 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| nickname | string | 否 | 昵称 |
| avatar | string | 否 | 头像URL |
| gender | string | 否 | 性别 |
| bio | string | 否 | 个人简介 |
| phone | string | 否 | 手机号 |
- 请求示例:
json
{
"bio": "这是我的个人简介"
}
- 响应示例:
json
{
"code": 200,
"message": "更新成功",
"data": {
"id": 1,
"username": "example_user",
"nickname": null,
"avatar": "https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg",
"gender": "unknown",
"bio": "这是我的个人简介"
}
}
2️⃣功能实现

定义Pydantic类型
python
# 更新用户信息的模型类
class UserUpdateRequest(BaseModel):
nickname: str = None
avatar: str = None
gender: str = None
bio: str = None
phone: str = None
封装crud代码
python
# 更新用户信息:update更新->检查是否命中->获取更新后的用户返回
async def update_user_info(db: AsyncSession, username: str, user_data: UserRequest):
# update(User).where(User.username == username).values(字段=值,字段=值)
# user_data 是一个Pydantic类型,需要得到的数据类型是字典->**解包 使用model_dump()把数据转换成字典
# 没有设置值的不更新
query = update(User).where(User.username == username).values(**user_data.model_dump(
exclude_unset=True,
exclude_none= True
))
result = await db.execute(query)
await db.commit()
# 检查更新
if result.rowcount == 0:
return HTTPException(status_code=404, detail="用户不存在")
# 获取一下更新后的用户
updated_user = await get_user_by_username(db, username)
return updated_user
路由调用
python
# 修改用户信息:验证Token->更新(用户输入数据,put提交->请求体参数->定义Pydantic类型)->响应结果
# 参数:用户输入的+验证Token的+db(调用更新的方法)
@router.put("/update")
async def update_user_info(user_data: UserUpdateRequest, user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)):
user = await users.update_user_info(db, user.username, user_data)
return success_response(message="修改用户信息成功", data=UserInfoResponse.model_validate(user))
5、修改密码
1️⃣ 接口文档
- 接口地址 :
PUT /api/user/password - 请求头: 需要认证
- 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| oldPassword | string | 是 | 当前密码 |
| newPassword | string | 是 | 新密码 |
- 请求示例:
json
{
"oldPassword": "current_password",
"newPassword": "new_password"
}
- 响应示例:
json
{
"code": 200,
"message": "密码修改成功",
"data": null
}
2️⃣ 功能实现

先定义Pydantic模型类
python
class UserChangePasswordRequest(BaseModel):
old_password: str = Field(..., alias="oldPassword", description="旧密码")
new_password: str = Field(..., min_length=6, alias="newPassword", description="新密码")
封装crud代码
python
# 修改密码:验证旧密码->新密码加密->修改密码
async def change_password(db: AsyncSession, user: User, old_password: str, new_password: str):
if not security.verify_password(old_password, user.password):
return False
hashed_new_password = security.get_hash_password(new_password)
user.password = hashed_new_password
# 更新:由SQLAlchemy真正接管这个User对象,确保可以commit
# 规避 session 过期或关闭导致的不能提交的问题
db.add(user)
await db.commit()
await db.refresh(user)
return True
路由调用
python
@router.put("/password")
async def update_password(
password_data: UserChangePasswordRequest,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
if not await users.change_password(db, user, password_data.old_password, password_data.new_password):
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="修改密码失败,请稍后再试")
return success_response(message="修改密码成功")
6、总结

1、封装通用成功响应格式
① 封装函数,返回统一响应格式
② 定义模型类
③ 调用函数,响应结果
2、全局异常处理器:用于捕获业务层、数据库层以及系统层抛出的异常,并以统一的响应格式返回给前端。
① 定义异常处理器(函数)
② 全局注册异常处理器:app.add **exception **handler(异常类, 异常处理函数名)
3、passlib 加密
① 安装 passlib:passlib[bcrypt]==1.7.4
② 创建加密上下文:pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
③ 加密或校验
pwd_context.hash(password)pwd_context.verify(plain_password, hashed_password)
4、用户访问令牌 Token
Token:是服务器发给客户端的一段字符串,用来在后续请求中证明"你已经登录过了"
作用:解决 HTTP 是无状态的问题,在每次请求中"自我证明身份"
① 后端生成 Token:str( uuid.uuid4() )
② 从请求头获取 Token:authorization: str=Header(...)
2、收藏模块
1、检查新闻收藏状态
1️⃣ 接口文档
- 接口地址 :
GET /api/favorite/check - 请求头: 需要认证
- 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| newsId | integer | 是 | 新闻ID |
- 请求示例:
plain
GET /api/favorite/check?newsId=1
- 响应示例:
json
{
"code": 200,
"message": "success",
"data": {
"isFavorite": true
}
}
2️⃣ 功能实现

收藏表模型类
python
from datetime import datetime
from sqlalchemy import UniqueConstraint, Index, Integer, ForeignKey, DateTime
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from models.news import News
from models.users import User
class Base(DeclarativeBase):
pass
class Favorite(Base):
"""
收藏表ORM模型
"""
__tablename__ = 'favorite'
# 创建索引
__table_args__ = (
# 唯一约束,当前用户只能收藏一次
UniqueConstraint('user_id', 'news_id', name='user_news_unique'),
Index('fk_favorite_user_idx', 'user_id'),
Index('fk_favorite_news_idx', 'news_id'),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment="收藏ID")
user_id: Mapped[int] = mapped_column(Integer, ForeignKey(User.id), nullable=False, comment="用户ID")
news_id: Mapped[int] = mapped_column(Integer, ForeignKey(News.id), nullable=False, comment="新闻ID")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False, comment="收藏时间")
def __repr__(self):
return f"<Favorite(id={self.id}, user_id={self.user_id}, news_id={self.news_id}, created_at={self.created_at})>"
路由接口
python
from fastapi import APIRouter, Query, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from config.db_config import get_db
from models.users import User
from utils.auth import get_current_user
from utils.response import success_response
router = APIRouter(prefix="/api/favorite",tags=["favorite"])
@router.get("/check")
async def check_favorite(
user_id: int = Query(...,alias="userId"),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
return success_response(message="检查收藏成功")
python
from routers import news, users,favorite
app.include_router(favorite.router)
封装crud方法
python
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from models.favorite import Favorite
# 检查收藏状态:当前用户 是否收藏了这一条新闻
async def is_news_favorite(
db: AsyncSession,
user_id: int,
news_id: int
):
query = select(Favorite).where(Favorite.user_id == user_id, Favorite.news_id == news_id)
result = await db.execute(query)
# 是否有收藏记录
return result.scalar_one_or_none() is not None
构造响应数据的Pydantic模型类
python
from pydantic import BaseModel,Field
class FavoriteCheckResponse(BaseModel):
"""
收藏状态响应模型
"""
is_favorite: bool = Field(...,alias="isFavorite")
路由调用
python
from fastapi import APIRouter, Query, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from config.db_config import get_db
from models.users import User
from schemas.favorite import FavoriteCheckResponse
from utils.auth import get_current_user
from utils.response import success_response
from crud import favorite
router = APIRouter(prefix="/api/favorite",tags=["favorite"])
@router.get("/check")
async def check_favorite(
user_id: int = Query(...,alias="userId"),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
is_favorited = await favorite.is_news_favorite(db, user_id, user.id) # 得到的是一个布尔值,需要把整个对象构造出来
return success_response(message="检查收藏成功",data=FavoriteCheckResponse(isFavorite=is_favorited))
2、添加收藏
1️⃣ 接口文档
- 接口地址 :
POST /api/favorite/add - 请求头: 需要认证
- 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| newsId | integer | 是 | 新闻ID |
- 请求示例:
json
{
"newsId": 1
}
- 响应示例:
json
{
"code": 200,
"message": "收藏成功",
"data": {
"id": 1,
"userId": 1,
"newsId": 1,
"createTime": "2023-01-01T00:00:00"
}
}
2️⃣ 功能实现

添加收藏请求体参数的模型类型
python
class FavoriteAddRequest(BaseModel):
"""
收藏请求模型
"""
news_id: int = Field(...,alias="newsId")
封装crud代码
python
async def add_news_favorite(
db: AsyncSession,
user_id: int,
news_id: int
):
favorite = Favorite(user_id=user_id, news_id=news_id)
db.add(favorite)
await db.commit()
await db.refresh(favorite)
return favorite
路由调用
python
@router.post("/add")
async def add_favorite(
data: FavoriteAddRequest,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
result = await favorite.add_news_favorite(db, user.id,data.news_id)
return success_response(message="收藏成功",data=result)
3、取消收藏
1️⃣接口文档
- 接口地址 :
DELETE /api/favorite/remove - 请求头: 需要认证
- 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| newsId | integer | 是 | 新闻ID |
- 请求示例:
plain
DELETE /api/favorite/remove?newsId=1
- 响应示例:
json
{
"code": 200,
"message": "取消收藏成功",
"data": null
}
2️⃣ 功能实现

封装crud方法
python
async def remove_news_favorite(
db: AsyncSession,
user_id: int,
news_id: int
):
stmt = delete(Favorite).where(Favorite.user_id == user_id, Favorite.news_id == news_id)
result = await db.execute(stmt)
await db.commit()
return result.rowcount > 0
路由调用
python
@router.delete("/remove")
async def remove_favorite(
news_id: int = Query(...,alias="newsId"),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
result = await favorite.remove_news_favorite(db, user.id, news_id)
if not result:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail="收藏记录不存在")
return success_response(message="取消收藏成功")
3、收藏列表
1️⃣ 接口文档
- 接口地址 :
GET /api/favorite/list - 请求头: 需要认证
- 请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | integer | 否 | 页码,默认为1 |
| pageSize | integer | 否 | 每页条数,默认为10,最大值为100 |
- 请求示例:
plain
GET /api/favorite/list
GET /api/favorite/list?page=1&pageSize=10
- 响应示例:
json
{
"code": 200,
"message": "success",
"data": {
"list": [
{
"id": 1,
"title": "新闻标题",
"description": "",
"image": "",
"author": "",
"publishTime": "2023-01-01T00:00:00",
"categoryId": 1,
"views": 1,
"favoriteTime": "2023-01-01T00:00:00"
}
],
"total": 1,
"hasMore": false
}
}
2️⃣ 功能实现

封装curd
python
# 获取收藏列表:获取的是某个用户的收藏列表 + 分页功能
async def get_favorite_list(
db: AsyncSession,
user_id: int,
page: int = 1,
page_size: int = 10
):
# 总量、收藏的新闻列表
count_query = select(func.count()).where(Favorite.user_id == user_id)
count_result = await db.execute(count_query)
total = count_result.scalar_one()
# 获取收藏列表 - 联表查询 join() + 按照收藏时间进行排序 + 分页
# select(查询主体模型类,字段别名).join(联合查询的模型类,联合查询的条件).where().order_by().offset().limit()
# 别名:Favorite.created_at.label("favorite_time")
# rows是一个列表,列表中有一个元组[
# (新闻对象,收藏时间,收藏id)
# ]
query = (select(News, Favorite.created_at.label("favorite_time"), Favorite.id.label("favorite_id"))
.join(Favorite, Favorite.news_id == News.id)
.where(Favorite.user_id == user_id)
.order_by(Favorite.created_at.desc())
.offset((page - 1) * page_size)
.limit(page_size))
result = await db.execute(query)
rows = result.all()
return rows, total
规划两个类: 一个是新闻模型类 + 收藏的模型类
python
# 规划两个类: 一个是新闻模型类 + 收藏的模型类
class FavoriteNewsItemResponse(NewsItemBase):
"""
收藏列表接口响应模型类
"""
favorite_id: int = Field(...,alias="favoriteId")
favorite_time: datetime = Field(...,alias="favoriteTime")
model_config = ConfigDict(
populate_by_name=True, # alias/字段名兼容
from_attributes=True # 允许从ORM对象属性中取值
)
# 收藏列表接口响应模型类
class FavoriteListResponse(BaseModel):
"""
收藏列表接口响应模型类
"""
list: list[FavoriteNewsItemResponse]
total: int
has_more: bool = Field(alias="hasMore")
model_config = ConfigDict(
populate_by_name=True, # alias/字段名兼容
from_attributes=True # 允许从ORM对象属性中取值
)
python
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
class NewsItemBase(BaseModel):
id: int
title: str
description: Optional[str] = None
image: Optional[str] = None
author: Optional[str] = None
category_id: int = Field(alias="categoryId")
views: int
publish_time: Optional[datetime] = Field(None, alias="publishedTime")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True
)
路由调用
python
# 获取收藏列表
@router.get("/list")
async def get_favorite_list(
page: int = Query(1,ge=1),
page_size: int = Query(10,ge=1,le=100,alias="pageSize"),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
rows,total = await favorite.get_favorite_list(db, user.id, page, page_size)
favorite_list = [{
**news.__dict__,
"favorite_time": favorite_time.isoformat(),
"favorite_id": favorite_id
} for news,favorite_time,favorite_id in rows]
has_more = total > page * page_size
data = FavoriteListResponse(list=favorite_list,total=total,hasMore=has_more)
return success_response(message="获取收藏列表成功",data=data)
4、清空收藏列表
1️⃣ 接口文档
- 接口地址 :
DELETE /api/favorite/clear - 请求头: 需要认证
- 响应示例:
json
{
"code": 200,
"message": "成功删除1条收藏记录",
"data": null
}
2️⃣功能实现

封装crud代码
python
# 清空收藏列表:清空某个用户的收藏列表
async def remove_all_favorite(
db: AsyncSession,
user_id: int
):
stmt = delete(Favorite).where(Favorite.user_id == user_id)
result = await db.execute(stmt)
await db.commit()
# 返回一个删除的数量
return result.rowcount or 0
路由调用
python
@router.delete("/clear")
async def clear_favorite(
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
count = await favorite.remove_all_favorite(db, user.id)
return success_response(message=f"清空了{count}条收藏记录")
5、小结
1、ORM 联表查询的写法
select ( 查询的主体模型类, 字段重命名 ).join( jion 的模型类, join 的条件)
2、模型类中,UniqueConstraint 的作用
唯一约束
3、缓存和模型调用
1、Redis
缓存:高速的临时数据存储机制

1️⃣ 数据缓存
缓存是一种存储机制,用于临时存储数据或计算结果,当再次需要这些数据时,可以快速从缓存中检索,而不是重新进行耗时或昂贵的获取和计算过程。
在网站开发中,缓存(Cache)是一个非常重要的概念,其核心作用是提高性能、降低延迟和减轻服务器负载。
主要优势:
- 提升性能和用户体验
- 减轻服务器/数据库负载
- 降低网络延迟
- 节省资源和成本

2️⃣ 项目中的缓存流程


3️⃣ Redis
Redis 是一种高性能的 Key-Value** **** 存储系统**,它将数据存储在内存中,因此读写速度极快,非常适合作为应用层的缓存服务。
在 FastAPI 这样的后端框架中,通常在应用层使用像Redis 这样的内存**** ****数据存储作为缓存。

4️⃣ 安装 Redis 服务端


5️⃣ 配置 Redis 客户端
1、安装 Redis 客户端
python
pip install redis
2、配置 Redis 客户端
import redis.asyncio as redis
redis.Redis(...)
- host: Redis 服务器地址
- port: 端口号 6379
- db: 数据库编号(0~15)
- decode_responses: 是否将返回的数据从字节流解码为字符串

python
import redis.asyncio as redis
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
# 创建Redis的连接对象
redis_client = redis.Redis(
host=REDIS_HOST, # Redis服务器的IP地址
port=REDIS_PORT, # Redis服务端口号
db=REDIS_DB, # Redis数据库编号,0-15,默认为0
decode_responses=True # 是否自动将返回值解码为字符串
)
6️⃣ 封装缓存操作
缓存操作就是围绕 Redis 做"存、取、删、判断、过期"等操作,让数据访问更快、数据库压力更小。
Redis 存储数据:key - value
python
import json
from typing import Any
import redis.asyncio as redis
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
# 创建Redis的连接对象
redis_client = redis.Redis(
host=REDIS_HOST, # Redis服务器的IP地址
port=REDIS_PORT, # Redis服务端口号
db=REDIS_DB, # Redis数据库编号,0-15,默认为0
decode_responses=True # 是否自动将返回值解码为字符串
)
# 设置和读取(字符串和列表或字典)"[{}]"
# 读取:字符串
async def get_cache(key: str):
# return await redis_client.get( key)
try:
return await redis_client.get(key)
except Exception as e:
print(f"获取缓存失败: {e}")
return None
# 读取:列表或字典
async def get_json_cache(key: str):
try:
data = await redis_client.get(key)
if data:
return json.loads(data) # 序列化
return None
except Exception as e:
print(f"获取 JSON 缓存失败: {e}")
return None
# 设置缓存 setex(key, expire, value)
async def set_cache(key: str, value: Any, expire: int = 3600):
try:
if isinstance(value, (dict,list)):
# 转字符串
value = json.dumps(value,ensure_ascii= False) # 中文正常保存
await redis_client.setex(key, expire, value)
return True
except Exception as e:
print(f"设置缓存失败: {e}")
return False

7️⃣ 设计缓存策略
旁路缓存策略(Cache-Aside) 是一种常见 的缓存策略,其核心概念是应用程序主动管理缓存,在读取数据时先检查缓存 ,如果缓存中没有数据 ,则从数据库或其他**** ****数据源 加载数据,并将数据存入缓存 ;当数据更新 或删除 时,应用程序也负责更新或删除缓存中的数据。
不同类型的数据,缓存时间不同,否则会出现缓存雪崩
数据越稳定,缓存越久;数据变化越快,缓存越短
python
# 新闻相关的缓存方法:新闻分类的读取和写入
# key - value
from typing import List, Dict, Any
from config.cache_conf import get_json_cache, set_cache
CATEGORIES_KEY = "news:categories"
# 获取新闻分类缓存
async def get_cached_categories():
return await get_json_cache(CATEGORIES_KEY)
# 写入新闻分类缓存:缓存的数据,过期时间
# 分类、配置 7200;列表:600;详情:1800;验证码:120 - 数据越稳定,缓存时间越持久
# 避免所有key同时过期 引起缓存雪崩
async def set_cached_categories(data: List[Dict[str,Any]], expire: int = 7200):
return await set_cache(CATEGORIES_KEY, data, expire)
改造新闻的crud代码,加入缓存
python
from fastapi.encoders import jsonable_encoder
from sqlalchemy import select, func, update
from sqlalchemy.ext.asyncio import AsyncSession
from cache.news_cache import get_cached_categories, set_cached_categories
# 导入模型类
from models.news import Category, News
async def get_categories(db: AsyncSession, skip: int = 0, limit: int = 100):
# 先尝试从缓存中获取数据
cached_categories = await get_cached_categories()
if cached_categories:
return cached_categories
stmt = select(Category).offset(skip).limit(limit)
result = await db.execute(stmt)
# return result.scalars().all()
categories = result.scalars().all() # ORM结果
# 写入缓存
if categories:
categories =jsonable_encoder(categories)
await set_cached_categories(categories)
# 返回数据
return categories
async def get_news_list(db: AsyncSession, category_id: int, skip: int = 0, limit: int = 10):
# 查询指定分类下的所有新闻,limit指定每页展示的数量
stmt = select(News).where(News.category_id == category_id).offset(skip).limit(limit)
result = await db.execute(stmt)
return result.scalars().all()
async def get_news_count(db: AsyncSession, category_id: int):
# 查询指定分类下的所有新闻的总量
stmt = select(func.count(News.id)).where(News.category_id == category_id)
result = await db.execute(stmt)
return result.scalar_one() # 只能有一个结果,否则报错
async def get_news_detail(db: AsyncSession, news_id: int):
# 根据新闻ID查询新闻
stmt = select(News).where(News.id == news_id)
result = await db.execute(stmt)
return result.scalar_one_or_none()
async def increase_news_views(db: AsyncSession, news_id: int):
# 增加新闻的浏览量
stmt = update(News).where(News.id == news_id).values(views=News.views + 1)
result = await db.execute(stmt)
# 更新之后,立刻提交事务
await db.commit()
# 更新->检查数据库是否真的命中了数据->命中了返回True
return result.rowcount > 0
async def get_related_news(db: AsyncSession, news_id: int, category_id: int, limit: int = 5):
# 获取新闻的关联新闻,order_by 排序->浏览量和发布时间排序
stmt = select(News).where(
News.id != news_id,
News.category_id == category_id
).order_by(
News.views.desc(), # 默认是升序,desc是降序
News.publish_time.desc()
).limit(limit)
result = await db.execute(stmt)
# return result.scalars().all() # 这个方法会返回所有的字段
related_news = result.scalars().all()
# 这里使用一个列表推导式,推导出新闻的核心数据(字段),然后再return
return [{
"id": news_detail.id,
"title": news_detail.title,
"content": news_detail.content,
"image": news_detail.image,
"author": news_detail.author,
"publishTime": news_detail.publish_time,
"categoryId": news_detail.category_id,
"views": news_detail.views,
} for news_detail in related_news]
缓存新闻列表
- 缓存 Key(唯一):news:list:分类 ID:页码:每页数量
- 缓存 Value:新闻列表

先实现缓存的方法
python
# 新闻相关的缓存方法:新闻分类的读取和写入
# key - value
from typing import List, Dict, Any, Optional
from config.cache_conf import get_json_cache, set_cache
CATEGORIES_KEY = "news:categories"
NEWS_LIST_PREFIX = "news_list:"
# 获取新闻分类缓存
async def get_cached_categories():
return await get_json_cache(CATEGORIES_KEY)
# 写入新闻分类缓存:缓存的数据,过期时间
# 分类、配置 7200;列表:600;详情:1800;验证码:120 - 数据越稳定,缓存时间越持久
# 避免所有key同时过期 引起缓存雪崩
async def set_cached_categories(data: List[Dict[str,Any]], expire: int = 7200):
return await set_cache(CATEGORIES_KEY, data, expire)
# 写入缓存-新闻列表 key = new_list:分类id:页码:每页数量 + 列表数据 + 过期时间
async def set_cache_news_list(category_id: Optional[int], page: int, size: int, new_list: List[Dict[str, Any]], expire: int = 1800):
# 调用封装Redis设置的方法,存新闻列表到缓存
category_part = "all" if category_id is None else category_id
key = f"{NEWS_LIST_PREFIX}{category_part}:{page}:{size}"
return await set_cache(key, new_list, expire)
# 读取缓存-新闻列表
async def get_cache_news_list(category_id: Optional[int], page: int, size: int):
category_part = "all" if category_id is None else category_id
key = f"{NEWS_LIST_PREFIX}{category_part}:{page}:{size}"
return await get_json_cache(key)
接着调用封装crud
python
from fastapi.encoders import jsonable_encoder
from sqlalchemy import select, func, update
from sqlalchemy.ext.asyncio import AsyncSession
from cache.news_cache import get_cached_categories, set_cached_categories, get_cache_news_list, set_cache_news_list
# 导入模型类
from models.news import Category, News
from schemas.base import NewsItemBase
async def get_categories(db: AsyncSession, skip: int = 0, limit: int = 100):
# 先尝试从缓存中获取数据
cached_categories = await get_cached_categories()
if cached_categories:
return cached_categories
stmt = select(Category).offset(skip).limit(limit)
result = await db.execute(stmt)
# return result.scalars().all()
categories = result.scalars().all() # ORM结果
# 写入缓存
if categories:
categories = jsonable_encoder(categories)
await set_cached_categories(categories)
# 返回数据
return categories
async def get_news_list(db: AsyncSession, category_id: int, skip: int = 0, limit: int = 10):
# 先尝试从缓存中获取数据
# 跳过的数量skip = (页码-1) * 每页数量 -> 页码 = 跳过的数量 // 每页数量 + 1
# await get_cache_news_list(分类id,页码,每页数量)
page = skip // limit + 1
cached_list = await get_cache_news_list(category_id, page, limit) # 缓存数据 json
if cached_list:
# return cached_news_list # 这里要的是ORM,不是 json
return [News(**item) for item in cached_list]
# 查询指定分类下的所有新闻,limit指定每页展示的数量
stmt = select(News).where(News.category_id == category_id).offset(skip).limit(limit)
result = await db.execute(stmt)
# return result.scalars().all()
news_list = result.scalars().all()
# 写入缓存
if news_list:
# 先把ORM数据转换为字典才能写入缓存
# ORM转成Pydantic,再转成字典
# by_alias=True 不适用别名,保存Python风格,因为Redis数据是给后端用的
new_data = [NewsItemBase.model_validate(item).model_dump(mode="json", by_alias=True) for item in news_list]
await set_cache_news_list(category_id, page, limit, new_data)
return news_list
async def get_news_count(db: AsyncSession, category_id: int):
# 查询指定分类下的所有新闻的总量
stmt = select(func.count(News.id)).where(News.category_id == category_id)
result = await db.execute(stmt)
return result.scalar_one() # 只能有一个结果,否则报错
async def get_news_detail(db: AsyncSession, news_id: int):
# 根据新闻ID查询新闻
stmt = select(News).where(News.id == news_id)
result = await db.execute(stmt)
return result.scalar_one_or_none()
async def increase_news_views(db: AsyncSession, news_id: int):
# 增加新闻的浏览量
stmt = update(News).where(News.id == news_id).values(views=News.views + 1)
result = await db.execute(stmt)
# 更新之后,立刻提交事务
await db.commit()
# 更新->检查数据库是否真的命中了数据->命中了返回True
return result.rowcount > 0
async def get_related_news(db: AsyncSession, news_id: int, category_id: int, limit: int = 5):
# 获取新闻的关联新闻,order_by 排序->浏览量和发布时间排序
stmt = select(News).where(
News.id != news_id,
News.category_id == category_id
).order_by(
News.views.desc(), # 默认是升序,desc是降序
News.publish_time.desc()
).limit(limit)
result = await db.execute(stmt)
# return result.scalars().all() # 这个方法会返回所有的字段
related_news = result.scalars().all()
# 这里使用一个列表推导式,推导出新闻的核心数据(字段),然后再return
return [{
"id": news_detail.id,
"title": news_detail.title,
"content": news_detail.content,
"image": news_detail.image,
"author": news_detail.author,
"publishTime": news_detail.publish_time,
"categoryId": news_detail.category_id,
"views": news_detail.views,
} for news_detail in related_news]
2、调用 QWen 大模型
1️⃣ QWen 模型
在前端页面中有模型请求的接口指定,支持千问(Qwen)系列模型的使用,可以直接指定对应的API和key
使用步骤:
登录阿里云百炼模型广场 → 选择模型(通义千问3-Max)→ API 参考 → 获取 HTTP 请求地址 + API Key → 前端调用

2️⃣ 前端调用模型
前端项目 → src/ → config/ → api.js → 替换 大模型 HTTP 请求地址 + API Key + 模型


3、小结
Redis 是一种高性能的 Key-Value 存储系统,它将数据存储在内存中,因此读写速度极快,非常适合作为应用层的
缓存服务。
