
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



