| """ |
| 文件缓存核心逻辑 - LRU缓存 + 后台清理 |
| """ |
|
|
| import time |
| import asyncio |
| from collections import OrderedDict |
| from typing import Optional, Tuple |
|
|
| from app.utils.url_input_handler import resolve_content, encode_text_to_base64, decode_base64_to_text, encode_bytes_to_base64 |
|
|
| MAX_CACHE_SIZE = 100 |
| CLEANUP_INTERVAL = 60 |
|
|
|
|
| class LRUCache: |
| """LRU缓存,自动淘汰最久未使用的条目""" |
|
|
| def __init__(self, max_size: int = MAX_CACHE_SIZE): |
| self._store: OrderedDict[str, dict] = OrderedDict() |
| self._max_size = max_size |
| self._cleanup_task: Optional[asyncio.Task] = None |
| self._lock = asyncio.Lock() |
|
|
| async def start_cleanup(self): |
| """启动后台清理任务""" |
| if self._cleanup_task is None: |
| self._cleanup_task = asyncio.create_task(self._cleanup_loop()) |
|
|
| async def stop_cleanup(self): |
| """停止后台清理任务""" |
| if self._cleanup_task is not None: |
| self._cleanup_task.cancel() |
| try: |
| await self._cleanup_task |
| except asyncio.CancelledError: |
| pass |
| self._cleanup_task = None |
|
|
| async def _cleanup_loop(self): |
| """定期清理过期条目""" |
| while True: |
| await asyncio.sleep(CLEANUP_INTERVAL) |
| await self._cleanup_expired() |
|
|
| async def _cleanup_expired(self): |
| """清理所有过期条目""" |
| async with self._lock: |
| now = time.time() |
| expired = [k for k, v in self._store.items() if v["expires_at"] < now] |
| for k in expired: |
| del self._store[k] |
|
|
| async def set(self, key: str, value: str, ttl_seconds: int) -> dict: |
| """设置缓存,自动淘汰 LRU 条目""" |
| async with self._lock: |
| if key in self._store: |
| del self._store[key] |
|
|
| while len(self._store) >= self._max_size: |
| self._store.popitem(last=False) |
|
|
| self._store[key] = { |
| "value": value, |
| "expires_at": time.time() + ttl_seconds, |
| } |
| self._store.move_to_end(key) |
| return {"success": True, "message": f"缓存已设置,TTL {ttl_seconds}秒"} |
|
|
| async def get(self, key: str) -> Optional[dict]: |
| """获取缓存,更新 LRU位置""" |
| async with self._lock: |
| entry = self._store.get(key) |
| if not entry: |
| return None |
|
|
| if time.time() > entry["expires_at"]: |
| del self._store[key] |
| return None |
|
|
| self._store.move_to_end(key) |
| return entry |
|
|
| async def delete(self, key: str) -> bool: |
| """删除缓存""" |
| async with self._lock: |
| if key in self._store: |
| del self._store[key] |
| return True |
| return False |
|
|
| async def stats(self) -> dict: |
| """获取缓存统计""" |
| async with self._lock: |
| return { |
| "size": len(self._store), |
| "max_size": self._max_size, |
| } |
|
|
|
|
| |
| _cache: Optional[LRUCache] = None |
|
|
|
|
| def get_cache() -> LRUCache: |
| """获取全局缓存实例""" |
| global _cache |
| if _cache is None: |
| _cache = LRUCache() |
| return _cache |
|
|
|
|
| async def init_cache(): |
| """初始化缓存并启动清理任务""" |
| cache = get_cache() |
| await cache.start_cleanup() |
|
|
|
|
| async def shutdown_cache(): |
| """关闭缓存清理任务""" |
| global _cache |
| if _cache is not None: |
| await _cache.stop_cleanup() |
|
|
|
|
| async def do_set(key: str, value_base64: str, ttl_seconds: int = 3600) -> dict: |
| """设置缓存值""" |
| cache = get_cache() |
| return await cache.set(key, value_base64, ttl_seconds) |
|
|
|
|
| async def do_get(key: str) -> dict: |
| """获取缓存值""" |
| cache = get_cache() |
| entry = await cache.get(key) |
| if not entry: |
| return {"success": False, "message": f"缓存键 {key} 不存在或已过期"} |
| return {"success": True, "value": entry["value"], "message": "缓存获取成功"} |
|
|
|
|
| async def do_delete(key: str) -> dict: |
| """删除缓存值""" |
| cache = get_cache() |
| if await cache.delete(key): |
| return {"success": True, "message": f"缓存键 {key} 已删除"} |
| return {"success": False, "message": f"缓存键 {key} 不存在"} |
|
|
|
|
| async def do_set_text(key: str, text_content: str, ttl_seconds: int = 3600) -> dict: |
| """设置文本缓存 - 自动编码为 base64""" |
| value_base64 = encode_text_to_base64(text_content) |
| return await do_set(key, value_base64, ttl_seconds) |
|
|
|
|
| async def do_get_text(key: str) -> dict: |
| """获取文本缓存 - 自动从 base64 解码""" |
| result = await do_get(key) |
| if result["success"]: |
| try: |
| text = decode_base64_to_text(result["value"]) |
| return {"success": True, "text": text, "message": "获取成功"} |
| except Exception as e: |
| return {"success": False, "message": f"解码失败: {e}"} |
| return result |
|
|
|
|
| async def do_set_from_url(key: str, url: str, ttl_seconds: int = 3600) -> dict: |
| """从 URL 下载文件并缓存""" |
| try: |
| content, source = await resolve_content(None, url) |
| value_base64 = encode_bytes_to_base64(content) |
| result = await do_set(key, value_base64, ttl_seconds) |
| result["source"] = source |
| return result |
| except ValueError as e: |
| return {"success": False, "message": f"URL获取失败: {e}"} |