1- import contextvars
21from fastapi_cache import FastAPICache
32from functools import partial , wraps
43from typing import Optional , Any , Dict , Tuple
54from inspect import signature
6- from contextlib import asynccontextmanager
7- import asyncio
8- import random
9- from collections import defaultdict
10-
11- from apps .system .schemas .auth import CacheName
12- from apps .system .schemas .system_schema import UserInfoDTO
5+ from common .core .config import settings
136from common .utils .utils import SQLBotLogUtil
7+ from fastapi_cache .backends .inmemory import InMemoryBackend
148
15- # 使用contextvar来跟踪当前线程已持有的锁
16- _held_locks = contextvars .ContextVar ('held_locks' , default = set ())
17- # 高效锁管理器
18- class LockManager :
19- _locks = defaultdict (asyncio .Lock )
20-
21- @classmethod
22- def get_lock (cls , key : str ) -> asyncio .Lock :
23- return cls ._locks [key ]
24-
25- @asynccontextmanager
26- async def _get_cache_lock (key : str ):
27- # 获取当前已持有的锁集合
28- current_locks = _held_locks .get ()
29-
30- # 如果已经持有这个锁,直接yield(锁传递)
31- if key in current_locks :
32- yield
33- return
34-
35- # 否则获取锁并添加到当前上下文中
36- lock = LockManager .get_lock (key )
37- try :
38- await lock .acquire ()
39- # 更新当前持有的锁集合
40- new_locks = current_locks | {key }
41- token = _held_locks .set (new_locks )
42-
43- yield
44-
45- finally :
46- # 恢复之前的锁集合
47- _held_locks .reset (token )
48- if lock .locked ():
49- lock .release ()
9+ from fastapi_cache .decorator import cache as original_cache
5010
5111def custom_key_builder (
5212 func : Any ,
@@ -93,7 +53,6 @@ def cache(
9353 * ,
9454 cacheName : str , # 必须提供cacheName
9555 keyExpression : Optional [str ] = None ,
96- jitter : int = 60 , # 默认抖动60秒
9756):
9857 def decorator (func ):
9958 # 预先生成key builder
@@ -105,40 +64,22 @@ def decorator(func):
10564
10665 @wraps (func )
10766 async def wrapper (* args , ** kwargs ):
67+ if not settings .CACHE_TYPE or settings .CACHE_TYPE .lower () == "none" :
68+ return await func (* args , ** kwargs )
10869 # 生成缓存键
10970 cache_key = used_key_builder (
11071 func = func ,
111- namespace = namespace ,
72+ namespace = str ( namespace ) if namespace else "" ,
11273 args = args ,
11374 kwargs = kwargs
11475 )
11576
116- # 防击穿锁
117- async with _get_cache_lock (cache_key ):
118- backend = FastAPICache .get_backend ()
119-
120- # 双重检查
121- if (cached := await backend .get (cache_key )) is not None :
122- SQLBotLogUtil .debug (f"Cache hit: { cache_key } " )
123- if CacheName .USER_INFO .value in cache_key :
124- user = UserInfoDTO .model_validate (cached )
125- SQLBotLogUtil .info (f"User cache hit: [uid: { user .id } , account: { user .account } , oid: { user .oid } ]" )
126- return cached
127-
128- # 执行函数并缓存结果
129- result = await func (* args , ** kwargs )
130-
131- actual_expire = expire + random .randint (- jitter , jitter )
132- if await backend .get (cache_key ):
133- await backend .clear (cache_key )
134- await backend .set (cache_key , result , actual_expire )
135-
136- SQLBotLogUtil .debug (f"Cache set: { cache_key } (expire: { actual_expire } s)" )
137- if CacheName .USER_INFO .value in cache_key :
138- user = UserInfoDTO .model_validate (result )
139- SQLBotLogUtil .info (f"User cache set: [uid: { user .id } , account: { user .account } , oid: { user .oid } ]" )
140- return result
141-
77+ return await original_cache (
78+ expire = expire ,
79+ namespace = str (namespace ) if namespace else "" ,
80+ key_builder = lambda * _ , ** __ : cache_key
81+ )(func )(* args , ** kwargs )
82+
14283 return wrapper
14384 return decorator
14485
@@ -148,30 +89,47 @@ def clear_cache(
14889 cacheName : str ,
14990 keyExpression : Optional [str ] = None ,
15091):
151- """精确清除单个缓存项的装饰器"""
15292 def decorator (func ):
15393 @wraps (func )
15494 async def wrapper (* args , ** kwargs ):
95+ if not settings .CACHE_TYPE or settings .CACHE_TYPE .lower () == "none" :
96+ return await func (* args , ** kwargs )
15597 cache_key = custom_key_builder (
15698 func = func ,
157- namespace = namespace ,
99+ namespace = str ( namespace ) if namespace else "" ,
158100 args = args ,
159101 kwargs = kwargs ,
160102 cacheName = cacheName ,
161103 keyExpression = keyExpression ,
162104 )
163-
164- # 加锁防止竞争
165- async with _get_cache_lock (cache_key ):
166- backend = FastAPICache .get_backend ()
167- result = None
168- if await backend .get (cache_key ):
169- await backend .clear (cache_key )
170- result = await func (* args , ** kwargs )
171- if await backend .get (cache_key ):
172- await backend .clear (cache_key )
173- SQLBotLogUtil .info (f"Cache cleared: { cache_key } " )
174- return result
105+ backend = FastAPICache .get_backend ()
106+ if await backend .get (cache_key ):
107+ if settings .CACHE_TYPE .lower () == "redis" :
108+ redis = backend .redis
109+ await redis .delete (cache_key )
110+ else :
111+ await backend .clear (key = cache_key )
112+ SQLBotLogUtil .debug (f"Cache cleared: { cache_key } " )
113+ return await func (* args , ** kwargs )
175114
176115 return wrapper
177- return decorator
116+ return decorator
117+
118+
119+ def init_sqlbot_cache ():
120+ cache_type : str = settings .CACHE_TYPE
121+ if cache_type == "memory" :
122+ FastAPICache .init (InMemoryBackend ())
123+ SQLBotLogUtil .info ("SQLBot 使用内存缓存, 仅支持单进程模式" )
124+ elif cache_type == "redis" :
125+ from fastapi_cache .backends .redis import RedisBackend
126+ import redis .asyncio as redis
127+ from redis .asyncio .connection import ConnectionPool
128+ redis_url = settings .CACHE_REDIS_URL or "redis://localhost:6379/0"
129+ pool = ConnectionPool .from_url (url = redis_url )
130+ redis_client = redis .Redis (connection_pool = pool )
131+ FastAPICache .init (RedisBackend (redis_client ), prefix = "sqlbot-cache" )
132+ SQLBotLogUtil .info (f"SQLBot 使用Redis缓存, 可使用多进程模式" )
133+ else :
134+ SQLBotLogUtil .warning ("SQLBot 未启用缓存, 可使用多进程模式" )
135+
0 commit comments