feat:Redis缓存升级

This commit is contained in:
kakune55 2025-06-17 21:04:43 +08:00
parent e166ec615b
commit fbcb3953b7
8 changed files with 88 additions and 4 deletions

Binary file not shown.

View File

@ -3,6 +3,8 @@ from sqlalchemy.pool import QueuePool
from sqlalchemy import text from sqlalchemy import text
from app.models import UserRole, OrderStatus from app.models import UserRole, OrderStatus
import time import time
import redis
from redis import RedisError
def wait_for_db(max_retries=5, delay=5): def wait_for_db(max_retries=5, delay=5):
"""等待数据库连接可用""" """等待数据库连接可用"""
@ -27,8 +29,35 @@ def wait_for_db(max_retries=5, delay=5):
time.sleep(delay) time.sleep(delay)
return None return None
def init_redis(max_retries=3, delay=1):
"""初始化Redis连接池"""
redis_pool = redis.ConnectionPool(
host='localhost',
port=6379,
db=0,
max_connections=10,
decode_responses=True
)
for i in range(max_retries):
try:
r = redis.Redis(connection_pool=redis_pool)
if r.ping():
print(f"Redis连接成功 (尝试 {i+1}/{max_retries})")
return redis_pool
except RedisError as e:
if i == max_retries - 1:
raise
time.sleep(delay)
return None
# 使用连接池的数据库引擎 # 使用连接池的数据库引擎
engine = wait_for_db() engine = wait_for_db()
redis_pool = init_redis()
def get_redis():
"""获取Redis连接"""
return redis.Redis(connection_pool=redis_pool)
# 创建数据库表 # 创建数据库表
def init_db(): def init_db():

View File

@ -3,7 +3,7 @@ from sqlmodel import Session, select
from sqlalchemy import text from sqlalchemy import text
from .models import User, Order from .models import User, Order
from .schemas import UserLogin, OrderCreate, OrderUpdate, UserUpdate from .schemas import UserLogin, OrderCreate, OrderUpdate, UserUpdate
from .utils import hash_password, verify_password, create_jwt_token, decode_jwt_token, admin_required from .utils import hash_password, verify_password, create_jwt_token, decode_jwt_token, admin_required, cache_response
from .db import engine from .db import engine
from .schemas import OrderStatus from .schemas import OrderStatus
@ -47,6 +47,7 @@ def login_page():
# 订单管理接口 # 订单管理接口
@order_bp.route('/', methods=['GET']) @order_bp.route('/', methods=['GET'])
@jwt_required @jwt_required
@cache_response(ttl=30, key_prefix="orders")
def list_orders(): def list_orders():
with Session(engine) as session: with Session(engine) as session:
orders = session.exec(select(Order)).all() orders = session.exec(select(Order)).all()
@ -62,6 +63,8 @@ def create_order():
session.add(order) session.add(order)
session.commit() session.commit()
session.refresh(order) session.refresh(order)
from app.utils import invalidate_cache
invalidate_cache("orders")
return jsonify(order.dict()), 201 return jsonify(order.dict()), 201
@order_bp.route('/<int:order_id>', methods=['PUT']) @order_bp.route('/<int:order_id>', methods=['PUT'])
@ -80,6 +83,10 @@ def update_order(order_id):
setattr(order, field, value) setattr(order, field, value)
session.add(order) session.add(order)
session.commit() session.commit()
from app.utils import invalidate_cache
invalidate_cache("orders")
invalidate_cache("summary")
invalidate_cache("rating")
return jsonify(order.dict()) return jsonify(order.dict())
@order_bp.route('/<int:order_id>', methods=['DELETE']) @order_bp.route('/<int:order_id>', methods=['DELETE'])
@ -91,6 +98,10 @@ def delete_order(order_id):
return jsonify({'msg': 'Order not found'}), 404 return jsonify({'msg': 'Order not found'}), 404
session.delete(order) session.delete(order)
session.commit() session.commit()
from app.utils import invalidate_cache
invalidate_cache("orders")
invalidate_cache("summary")
invalidate_cache("rating")
return jsonify({'msg': 'Deleted'}) return jsonify({'msg': 'Deleted'})
@auth_bp.route('/users/<int:user_id>', methods=['PUT']) @auth_bp.route('/users/<int:user_id>', methods=['PUT'])
@ -114,6 +125,7 @@ def order_panel():
@order_bp.route('/summary') @order_bp.route('/summary')
@jwt_required @jwt_required
@cache_response(ttl=60, key_prefix="summary")
def get_order_summary(): def get_order_summary():
with Session(engine) as session: with Session(engine) as session:
# 查询all_orders_summary表按status分组统计count总和 # 查询all_orders_summary表按status分组统计count总和
@ -134,6 +146,7 @@ def get_order_summary():
@order_bp.route('/rating_summary') @order_bp.route('/rating_summary')
@jwt_required @jwt_required
@cache_response(ttl=60, key_prefix="rating")
def get_rating_summary(): def get_rating_summary():
with Session(engine) as session: with Session(engine) as session:
# 查询each_order_summary表按order_id和status分组统计 # 查询each_order_summary表按order_id和status分组统计
@ -175,6 +188,7 @@ def get_rating_summary():
}) })
@order_bp.route('/type_summary') @order_bp.route('/type_summary')
@jwt_required @jwt_required
@cache_response(ttl=60, key_prefix="type")
def get_type_summary(): def get_type_summary():
with Session(engine) as session: with Session(engine) as session:
# 查询order_type_summary表按order_type分组统计count总和 # 查询order_type_summary表按order_type分组统计count总和
@ -190,6 +204,7 @@ def get_type_summary():
@order_bp.route('/top_products') @order_bp.route('/top_products')
@jwt_required @jwt_required
@cache_response(ttl=60, key_prefix="top")
def get_top_products(): def get_top_products():
with Session(engine) as session: with Session(engine) as session:
# 查询top_products_summary表按sales_count降序获取前5条记录 # 查询top_products_summary表按sales_count降序获取前5条记录

View File

@ -2,6 +2,9 @@ import jwt
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime, timedelta from datetime import datetime, timedelta
from flask import current_app, request, jsonify from flask import current_app, request, jsonify
from functools import wraps
from app.db import get_redis
import json
def hash_password(password): def hash_password(password):
return generate_password_hash(password) return generate_password_hash(password)
@ -13,13 +16,21 @@ def create_jwt_token(user):
payload = { payload = {
'user_id': user.id, 'user_id': user.id,
'role': user.role, 'role': user.role,
'exp': datetime.utcnow() + timedelta(days=1) 'exp': datetime.utcnow() + timedelta(days=7)
} }
token = jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256') token = jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256')
# 缓存token
r = get_redis()
r.setex(f"token:{token}", int(timedelta(days=1).total_seconds()), "1")
return token return token
def decode_jwt_token(token): def decode_jwt_token(token):
try: try:
# 先检查Redis缓存
r = get_redis()
if not r.exists(f"token:{token}"):
return None
payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256']) payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256'])
return {'user_id': payload['user_id'], 'role': payload['role']} return {'user_id': payload['user_id'], 'role': payload['role']}
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:
@ -28,6 +39,7 @@ def decode_jwt_token(token):
return None return None
def admin_required(f): def admin_required(f):
@wraps(f)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
auth = request.headers.get('Authorization', None) auth = request.headers.get('Authorization', None)
if not auth or not auth.startswith('Bearer '): if not auth or not auth.startswith('Bearer '):
@ -37,5 +49,32 @@ def admin_required(f):
if not user_data or user_data.get('role') != 'admin': if not user_data or user_data.get('role') != 'admin':
return jsonify({'msg': 'Admin access required'}), 403 return jsonify({'msg': 'Admin access required'}), 403
return f(*args, **kwargs) return f(*args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper return wrapper
def cache_response(ttl=30, key_prefix="cache"):
"""缓存响应装饰器"""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
r = get_redis()
cache_key = f"{key_prefix}:{request.path}"
# 尝试从缓存获取
cached_data = r.get(cache_key)
if cached_data:
return jsonify(json.loads(cached_data))
# 执行函数并缓存结果
response = f(*args, **kwargs)
if response.status_code == 200:
r.setex(cache_key, ttl, json.dumps(response.json))
return response
return wrapper
return decorator
def invalidate_cache(key_prefix):
"""使缓存失效"""
r = get_redis()
keys = r.keys(f"{key_prefix}:*")
if keys:
r.delete(*keys)

Binary file not shown.

View File

@ -5,4 +5,5 @@ pyjwt
werkzeug werkzeug
flask-cors flask-cors
pymysql pymysql
pyecharts pyecharts
redis