【FastAPI】AI 掘金头条新闻系统—引入缓存

8 阅读4分钟

引入 redis 缓存

  1. 安装 redis 客户端 pip install redis
  2. 配置 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 字典。