Flask》》Flask-Caching缓存插件

csharp 复制代码
"""
Flask-Caching 完整示例
包含:内存缓存、Redis缓存、文件系统缓存、视图缓存、函数缓存、手动清除缓存
"""
import time
import json
from flask import Flask, jsonify, request, render_template_string
from flask_caching import Cache

# ==================== 1. 创建 Flask 应用 ====================
app = Flask(__name__)

# ==================== 2. 缓存配置 ====================
# 以下展示三种常用缓存配置,可以根据需要选择使用其中一种

# -------------------- 配置 A:SimpleCache(内存缓存,开发测试用)--------------------
# SimpleCache 使用 Python 字典在内存中存储,非线程安全,适合单进程开发环境
# 注意:生产环境不要使用 SimpleCache,因为它不支持多进程/多服务器共享
SIMPLE_CACHE_CONFIG = {
    'CACHE_TYPE': 'SimpleCache',           # 缓存类型:SimpleCache(内存缓存)
    'CACHE_DEFAULT_TIMEOUT': 300,          # 默认过期时间(秒),300秒 = 5分钟
    'CACHE_THRESHOLD': 500,                # 最大缓存条目数,超过后自动淘汰最旧的
    'CACHE_IGNORE_ERRORS': True,           # 遇到错误时是否忽略,True=忽略
}

# -------------------- 配置 B:RedisCache(生产环境推荐,支持分布式)--------------------
# Redis 是生产环境最常用的选择,支持多进程/多服务器共享缓存
# 使用前需要:1) 安装 Redis 服务器并启动 2) pip install redis
# 启动 Redis: `redis-server` 或 `docker run -d -p 6379:6379 redis`
REDIS_CACHE_CONFIG = {
    'CACHE_TYPE': 'RedisCache',            # 缓存类型:RedisCache
    'CACHE_REDIS_HOST': 'localhost',       # Redis 服务器主机地址
    'CACHE_REDIS_PORT': 6379,              # Redis 服务器端口,默认 6379
    'CACHE_REDIS_PASSWORD': '',            # Redis 密码,没有则留空
    'CACHE_REDIS_DB': 0,                   # 使用的 Redis 数据库编号(0-15)
    'CACHE_DEFAULT_TIMEOUT': 300,          # 默认过期时间(秒)
    'CACHE_KEY_PREFIX': 'flask_cache_',    # 缓存键的前缀,避免与其他应用冲突
}

# -------------------- 配置 C:FileSystemCache(单服务器场景)--------------------
# 使用文件系统存储缓存,适合单机部署且不方便使用 Redis 的场景
# 确保缓存目录有读写权限
FILESYSTEM_CACHE_CONFIG = {
    'CACHE_TYPE': 'FileSystemCache',       # 缓存类型:FileSystemCache
    'CACHE_DIR': './cache_data',           # 缓存存储的目录路径
    'CACHE_DEFAULT_TIMEOUT': 300,          # 默认过期时间(秒)
    'CACHE_THRESHOLD': 500,                # 最大缓存文件数
    'CACHE_IGNORE_ERRORS': True,           # 遇到错误时是否忽略
}

# ==================== 选择并应用配置 ====================
# 这里使用 SimpleCache 作为演示(无需额外安装,开箱即用)
# 如果安装了 Redis,可以把下面这行注释掉,改使用 REDIS_CACHE_CONFIG
app.config.from_mapping(SIMPLE_CACHE_CONFIG)

# 也可以从环境变量动态决定使用哪个配置(生产环境推荐)
# import os
# if os.environ.get('CACHE_TYPE') == 'redis':
#     app.config.from_mapping(REDIS_CACHE_CONFIG)
# else:
#     app.config.from_mapping(SIMPLE_CACHE_CONFIG)

# ==================== 3. 初始化缓存实例 ====================
cache = Cache(app)  # 传入 app 对象,自动读取 app.config 中的缓存配置

# 也可以使用延迟初始化方式(适合应用工厂模式):
# cache = Cache()  # 先不传 app
# cache.init_app(app)  # 后面再初始化

# ==================== 4. 路由:缓存使用示例 ====================

# ---------- 示例 1:简单的视图缓存 ----------
@app.route('/')
@cache.cached(timeout=60)  # 缓存此视图 60 秒
def index():
    """
    使用 @cache.cached 装饰器缓存视图的返回结果
    - 第一次访问时执行函数,结果存入缓存
    - 60 秒内的后续访问直接从缓存返回,绕过函数体
    - 默认缓存键基于请求的 URL path 生成
    """
    current_time = time.strftime('%Y-%m-%d %H:%M:%S')
    print(f"[INDEX] 函数被执行了,当前时间:{current_time}")  # 只有第一次或过期后才会打印
    return render_template_string("""
        <html>
        <body>
            <h1>Flask-Caching 演示</h1>
            <p>当前时间:{{ current_time }}</p>
            <p>如果 60 秒内刷新页面,时间不会变化(因为视图被缓存了)</p>
            <hr>
            <ul>
                <li><a href="/user/1">查看用户 1(缓存 30 秒)- 使用 key_prefix</a></li>
                <li><a href="/memoized/5">斐波那契计算(memoize)- 演示函数级缓存</a></li>
                <li><a href="/manual">手动缓存示例</a></li>
                <li><a href="/delete/user/1">删除 user/1 的缓存</a></li>
                <li><a href="/clear-all">清空全部缓存</a></li>
            </ul>
        </body>
        </html>
    """, current_time=current_time)


# ---------- 示例 2:带参数的视图缓存 ----------
@app.route('/user/<int:user_id>')
@cache.cached(timeout=30, key_prefix='user_view')  # key_prefix 指定缓存键前缀
def get_user(user_id):
    """
    有参数的视图缓存
    - key_prefix:缓存键前缀,不指定则默认为 'view/' + request.path
    - 当 query_string=True 时,缓存键会包含 URL 查询参数,适合 GET 请求
    """
    print(f"[USER] 查询用户 {user_id},执行了数据库查询(演示)")
    # 模拟数据库查询
    time.sleep(0.5)  # 模拟耗时操作
    user_data = {
        'id': user_id,
        'name': f'用户{user_id}',
        'cached_at': time.strftime('%Y-%m-%d %H:%M:%S')
    }
    return jsonify(user_data)


# ---------- 示例 3:函数级缓存(memoize)- 缓存任意函数的返回值 ----------
@app.route('/fib/<int:n>')
def fib_demo(n):
    """演示 memoize 函数缓存"""
    result = expensive_fibonacci(n)
    return jsonify({'n': n, 'result': result})


@cache.memoize(timeout=60)  # memoize 根据函数参数自动生成缓存键
def expensive_fibonacci(n):
    """
    计算斐波那契数列的第 n 项(递归版本,非常耗时)
    使用 @cache.memoize 装饰器,根据参数 n 的值缓存结果
    - 第一次调用传入 n=35 时,花费时间计算并缓存
    - 后续再调用 expensive_fibonacci(35) 时,直接从缓存返回
    - 注意:参数必须是可哈希(hashable)的类型,常见类型如 int、str、tuple 都可以
    """
    print(f"[FIB] 正在计算斐波那契 fib({n})... 这很耗时!")
    if n <= 1:
        return n
    return expensive_fibonacci(n-1) + expensive_fibonacci(n-2)


# ---------- 示例 4:条件缓存(unless)----------
@app.route('/admin')
@cache.cached(timeout=60, unless=lambda: not is_admin())
def admin_panel():
    """
    unless 参数:如果返回 True,则跳过缓存
    这里只有当用户是管理员时才缓存,普通用户每次都要重新计算
    实际项目中可以从 session 或 request 中判断用户角色
    """
    return jsonify({'role': 'admin', 'data': '敏感数据'})


def is_admin():
    """模拟判断当前用户是否是管理员"""
    # 实际项目中从 request.headers 或 session 中获取
    # 这里简单演示,可以通过 URL 参数 ?admin=1 模拟
    return request.args.get('admin') == '1'


# ---------- 示例 5:手动缓存控制(get/set/delete)----------
@app.route('/manual', methods=['GET', 'POST'])
def manual_cache_demo():
    """
    手动控制缓存:不依赖装饰器,直接使用 cache.get() 和 cache.set()
    这种方式的优点是可以完全控制缓存逻辑,比如:
    - POST 请求(@cache.cached 默认基于 URL,无法区分 body 内容)
    - 需要根据请求体内容决定缓存键
    - 需要更精细的缓存粒度控制
    """
    if request.method == 'POST':
        # 从 POST 请求中获取数据
        data = request.json or {}
        user_id = data.get('user_id')
        
        if not user_id:
            return jsonify({'error': '需要提供 user_id'}), 400
        
        # 构造缓存键(手动方式更灵活)
        cache_key = f'user_data_{user_id}'
        
        # 检查缓存是否存在
        cached_data = cache.get(cache_key)
        if cached_data is not None:
            # 缓存命中:返回缓存数据,并标记来源
            result = json.loads(cached_data) if isinstance(cached_data, str) else cached_data
            result['from_cache'] = True
            return jsonify(result)
        
        # 缓存未命中:查询数据库或调用 API
        print(f"[MANUAL] 缓存未命中,查询用户 {user_id} 的数据")
        # 模拟耗时操作
        time.sleep(1)
        user_info = {
            'user_id': user_id,
            'name': f'用户{user_id}',
            'data': f'这是用户{user_id}的详细信息',
            'fetched_at': time.strftime('%Y-%m-%d %H:%M:%S'),
            'from_cache': False
        }
        
        # 存入缓存(设置 120 秒过期)
        # 注意:存储到 Redis 的值会被序列化,建议先转成字符串
        cache.set(cache_key, json.dumps(user_info), timeout=120)
        return jsonify(user_info)
    
    # GET 请求:返回使用说明页面
    return render_template_string("""
        <html>
        <body>
            <h2>手动缓存示例</h2>
            <p>使用 POST 请求发送 JSON 数据,例如:</p>
            <pre>
curl -X POST http://localhost:5000/manual \\
  -H "Content-Type: application/json" \\
  -d '{"user_id": 1}'
            </pre>
            <p>第一次请求会耗时(查询逻辑),第二次请求从缓存返回。</p>
            <hr>
            <form method="post" action="/manual" style="display: inline;">
                <input type="hidden" name="user_id" value="99">
                <button type="submit">测试手动缓存(user_id=99)</button>
            </form>
        </body>
        </html>
    """)


# ---------- 示例 6:删除指定缓存 ----------
@app.route('/delete/user/<int:user_id>')
def delete_user_cache(user_id):
    """
    删除指定用户的缓存
    使用 cache.delete(key) 方法
    """
    # 方式 1:删除视图缓存(需要知道缓存的键名)
    # @cache.cached 默认生成的 cache_key 格式为:'view/' + request.path
    # 对于 /user/1 这个路径,cache_key 是 'view//user/1'
    cache_key = f'view//user/{user_id}'
    deleted = cache.delete(cache_key)
    
    # 方式 2:如果使用了 memoize,需要用 delete_memoized
    # cache.delete_memoized(expensive_fibonacci, n=35)  # 删除特定参数的缓存
    # cache.delete_memoized(expensive_fibonacci)        # 删除该函数的所有缓存
    
    return jsonify({
        'status': 'deleted' if deleted else 'not_found or already expired',
        'cache_key': cache_key
    })


# ---------- 示例 7:清理所有缓存 ----------
@app.route('/clear-all')
def clear_all_cache():
    """
    清空整个缓存(谨慎使用!)
    生产环境建议只用于管理员接口
    """
    cache.clear()
    return jsonify({'status': 'all cache cleared'})


# ---------- 示例 8:获取缓存统计信息 ----------
@app.route('/cache-stats')
def cache_stats():
    """
    获取缓存统计信息
    注意:SimpleCache 和 Redis 的部分统计方法可能不可用
    """
    # 获取当前缓存实例的类型
    cache_type = type(cache.cache).__name__
    
    # 尝试获取缓存大小(SimpleCache 支持)
    cache_size = None
    if hasattr(cache.cache, '_cache'):
        cache_size = len(cache.cache._cache)
    
    return jsonify({
        'cache_type': cache_type,
        'cache_size': cache_size,
        'info': 'SimpleCache 不使用 Redis,如需详细统计请改用 RedisCache'
    })


# ---------- 示例 9:POST 请求的手动缓存(复杂场景)----------
@app.route('/api/search', methods=['POST'])
def search_api():
    """
    演示 POST 请求的手动缓存
    @cache.cached 默认基于 URL path 生成缓存键,无法区分 POST body 内容
    因此 POST 请求需要手动实现缓存逻辑
    """
    data = request.json or {}
    
    # 根据请求体内容生成唯一的缓存键
    # 注意:字典的键顺序可能不同,需要排序以确保相同内容生成相同键
    query = data.get('query', '')
    page = data.get('page', 1)
    
    # 构造缓存键(先排序确保一致性)
    cache_key = f'search:query={query}&page={page}'
    
    # 尝试从缓存获取
    cached_result = cache.get(cache_key)
    if cached_result is not None:
        return jsonify({
            'from_cache': True,
            'data': json.loads(cached_result) if isinstance(cached_result, str) else cached_result,
            'cached_at': None  # 可以单独存储
        })
    
    # 模拟执行搜索(耗时操作)
    print(f"[SEARCH] 执行搜索:query={query}, page={page}")
    import random
    time.sleep(1)  # 模拟搜索延迟
    
    # 生成搜索结果
    results = {
        'query': query,
        'page': page,
        'total': random.randint(10, 100),
        'items': [f'结果{i+1}' for i in range(5)],
        'searched_at': time.strftime('%Y-%m-%d %H:%M:%S')
    }
    
    # 存入缓存,超时时间 60 秒
    cache.set(cache_key, json.dumps(results), timeout=60)
    
    return jsonify({
        'from_cache': False,
        'data': results
    })


# ==================== 5. 启动应用 ====================
if __name__ == '__main__':
    print("=" * 50)
    print("Flask-Caching 演示应用")
    print(f"缓存类型: {app.config.get('CACHE_TYPE', '未配置')}")
    print(f"缓存默认超时: {app.config.get('CACHE_DEFAULT_TIMEOUT', 300)} 秒")
    print("=" * 50)
    app.run(debug=True, port=5000)

》》测试命令

csharp 复制代码
# 1. 启动应用
python app.py
# 2. 测试视图缓存(首次访问,会打印日志)
curl http://localhost:5000/
# 3. 测试带参数的视图缓存
curl http://localhost:5000/user/1   # 第一次,缓存未命中,执行函数
curl http://localhost:5000/user/1   # 第二次,缓存命中,不执行函数
# 4. 测试 memoize 函数缓存
curl http://localhost:5000/fib/35   # 第一次:计算耗时
curl http://localhost:5000/fib/35   # 第二次:直接从缓存返回
# 5. 测试手动缓存(POST 请求)
curl -X POST http://localhost:5000/manual -H "Content-Type: application/json" -d '{"user_id": 1}'
curl -X POST http://localhost:5000/manual -H "Content-Type: application/json" -d '{"user_id": 1}'
# 6. 测试删除缓存
curl http://localhost:5000/delete/user/1
# 7. 清除全部缓存
curl http://localhost:5000/clear-all




相关推荐
绿豆人4 小时前
Cache缓存项目学习2
学习·缓存
明如正午4 小时前
转换pdf文件为md文件【markitdown+pdf4llm】
python·pdf·markitdown·pdf4llm
咯哦哦哦哦4 小时前
Foundationpose环境配置【非conda--纯UV】(linux22.04+python3.10)
python·pip·uv
AC赳赳老秦5 小时前
项目闭环管理:用 OpenClaw 对接 Jira / 禅道,实现需求 - 任务 - 进度 - 验收全流程自动化
运维·人工智能·python·自动化·devops·jira·openclaw
fillwang5 小时前
间接料库存预警报告设计
python·rpa
冷小鱼5 小时前
Valkey 深度剖析:Redis 最佳平替的技术全景
数据库·redis·缓存·valkey
.柒宇.5 小时前
AI 掘金头条项目-新闻模块实现
数据库·后端·python·fastapi
Chockong5 小时前
06_yolox_s.onnx的推理验证
python·神经网络
七颗糖很甜5 小时前
台风数据免费获取教程
大数据·python·算法