引入 redis 缓存
- 安装 redis 客户端
pip install redis - 配置 redis 客户端
redis 配置
import json
from typing import Any
import redis.asyncio as redis
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 2
REDIS_PWD = "123456"
# 创建连接对象
redis_client = redis.Redis(
host=REDIS_HOST,
port=REDIS_PORT,
db=REDIS_DB,
password=REDIS_PWD,
decode_responses=True # 是否将字节数据解码为字符串
)
# 读字符串
async def get_cache(key: str):
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
# 设置缓存
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
cache方法
# 新闻相关的缓存方法: 新闻分类的读取和写入
from typing import Dict, List, Any, Optional
from config.cache_conf import get_json_cache, set_cache
CATEGORIES_KEY = "news:categories"
NEWS_LIST_KEY = "news:list:"
# 读取新闻分类
async def get_cached_categories():
return await get_json_cache(CATEGORIES_KEY)
# 写入新闻分类缓存
async def set_cache_categories(data: List[Dict[str, Any]], expire: int = 7200):
return await set_cache(CATEGORIES_KEY, data, expire)
# 写入新闻列表
async def set_cache_news_list(category_id: Optional[int], page: int, size: int, news_list: List[Dict[str, Any]], expire: int = 1800 ):
category_part = category_id if category_id is not None else "all"
key = f"{NEWS_LIST_KEY}{category_part}:{page}:{size}"
return await set_cache(key, news_list, expire)
# 读取新闻列表
async def get_cached_news_list(category_id: Optional[int], page: int, size: int):
category_part = category_id if category_id is not None else "all"
key = f"{NEWS_LIST_KEY}{category_part}:{page}:{size}"
return await get_json_cache(key)
crud:news_cache
async def get_categories(db: AsyncSession, skip: int = 0, limit: int = 100):
# 先尝试获取缓存
cache_categories = await get_cached_categories()
if cache_categories:
return cache_categories
stmt = select(Category).offset(skip).limit(limit)
result = await db.execute(stmt)
categories = result.scalars().all()
# 写入缓存
if categories:
categories = jsonable_encoder(categories)
await set_cache_categories(categories)
return categories
async def get_news_list(db: AsyncSession, category_id: int, skip: int = 0, limit: int = 100):
# 尝试从缓存获取
page = skip // limit + 1
cache_news_list = await get_cached_news_list(category_id, page, limit)
if cache_news_list:
# 要的是 ORM 格式
return [News(**item) for item in cache_news_list]
stmt = select(News).where(News.category_id == category_id).offset(skip).limit(limit)
result = await db.execute(stmt)
news_list = result.scalars().all()
# 写入缓存
if news_list:
# 把 ORM 数据 转为字典
news_data = [NewsItemBase.model_validate(item).model_dump(mode="json", by_alias=False) for item in news_list]
news_list = jsonable_encoder(news_list)
await set_cache_news_list(category_id, page,limit, news_data)
return news_list
news_data = [NewsItemBase.model_validate(item).model_dump(mode="json", by_alias=False) for item in news_list]
这段代码的核心作用是:将一组从数据库查出来的 ORM 对象(news_list),通过 Pydantic 模型(NewsItemBase)的“中转”,批量转换成标准的 Python 字典列表。
通常这步操作是为了将数据库数据转换成前端 API 可以直接使用的 JSON 格式。下面为你拆解每个部分的具体含义:
1. for item in news_list
news_list 是你从数据库查询出来的原始结果,里面的 item 是 ORM 模型对象(比如 SQLAlchemy 的实例)。普通的 ORM 对象是不能直接转成 JSON 返回给前端的。
2. NewsItemBase.model_validate(item)
NewsItemBase是一个继承自 Pydantic 的BaseModel的模型类。model_validate(item)是 Pydantic V2 的方法。它的作用是把刚刚那个普通的 ORM 对象item,映射并转换成一个 Pydantic 模型实例。- 注意:为了让这一步生效,你的
NewsItemBase内部通常需要配置model_config = ConfigDict(from_attributes=True),这样 Pydantic 才知道去读取 ORM 对象的属性。
3. .model_dump(mode="json", by_alias=False)
这是把 Pydantic 模型实例打散,变成一个标准的 Python 字典。里面的两个参数非常关键:
-
mode="json":告诉 Pydantic,转换出来的值必须是能直接被 JSON 序列化的格式。比如,如果数据库里有datetime时间对象,直接转字典会保留对象格式,但加上mode="json",时间就会自动变成"2026-05-23T10:30:00"这种标准的字符串格式。 -
by_alias=False:控制字典的“键(key)”叫什么名字。False(默认):字典的键使用你在NewsItemBase里定义的Python 变量名(比如user_name)。True:字典的键会使用你通过Field(alias="...")指定的别名(比如你想在接口里返回userName或_id,就会开启这个)。
4. [ ... for item in news_list]
最外层的方括号是一个列表推导式,表示把 news_list 里的每一个 ORM 对象都按照上面的流程处理一遍,最终得到一个包含多个字典的列表(list[dict])。
简单总结一下流程:
数据库 ORM 对象 ➡️ (通过 model_validate) ➡️ Pydantic 模型实例 ➡️ (通过 model_dump) ➡️ 干净的 Python 字典。