dfd8421dc6
**Storage Layer:** - Database 类(aiosqlite + WAL + 并发保护) - WikiPage 数据模型 - fix_dirty_states 恢复机制 - FTS5 全文搜索支持 - 完整的 SQLite 表结构 **Service Layer:** - CacheService(LRU 缓存 + TTL + 大小限制) - QueryService(查询服务) - ParserService(Markdown 解析) - IndexerService(索引服务) - GraphService(链接图服务) **Tool Layer (8 个 MCP Tools):** - wiki_query - FTS5 全文搜索 + 标签过滤 - memory_bridge - 按工具来源浏览 - wiki_status - 索引状态 - wiki_lint - 健康审计 - cross_linker - 缺失链接发现 - tag_taxonomy - 标签一致性 - wiki_synthesize - 跨概念综合分析 - daily_update - 日常维护 + hot.md 生成 **MCP Protocol Layer:** - MCP 协议解析和封装 - 工具注册和路由 - 错误处理和日志 - stdio 模式支持 **配置和部署:** - requirements.txt - config.example.yaml - ecosystem.config.cjs (PM2) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
"""
|
|
Service Layer - CacheService(缓存服务)
|
|
|
|
使用 LRU 缓存 + TTL 过期策略,防止内存泄漏。
|
|
|
|
参考设计文档:第 2.3 节
|
|
"""
|
|
|
|
import asyncio
|
|
import time
|
|
from collections import OrderedDict
|
|
from typing import Optional, Any
|
|
import fnmatch
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class CacheService:
|
|
"""缓存服务 - LRU 缓存 + TTL 过期"""
|
|
|
|
def __init__(self, max_size: int = 1000):
|
|
self.cache: OrderedDict[str, tuple] = OrderedDict() # key -> (value, expire_time)
|
|
self.max_size = max_size
|
|
self.lock = asyncio.Lock()
|
|
|
|
async def get(self, key: str) -> Optional[Any]:
|
|
"""获取缓存值(异步,带锁)"""
|
|
async with self.lock:
|
|
if key not in self.cache:
|
|
return None
|
|
|
|
value, expire_time = self.cache[key]
|
|
|
|
# 检查是否过期
|
|
if expire_time and time.time() > expire_time:
|
|
del self.cache[key]
|
|
logger.debug(f"Cache expired: {key}")
|
|
return None
|
|
|
|
# LRU: 移到末尾
|
|
self.cache.move_to_end(key)
|
|
return value
|
|
|
|
async def set(self, key: str, value: Any, ttl: int = 3600) -> None:
|
|
"""设置缓存值(异步,带锁)"""
|
|
async with self.lock:
|
|
expire_time = time.time() + ttl if ttl else None
|
|
|
|
# 如果缓存已满,删除最旧的条目
|
|
if len(self.cache) >= self.max_size and key not in self.cache:
|
|
self.cache.popitem(last=False) # FIFO 删除
|
|
logger.debug(f"Cache full, evicted oldest entry")
|
|
|
|
self.cache[key] = (value, expire_time)
|
|
self.cache.move_to_end(key)
|
|
logger.debug(f"Cache set: {key} (TTL={ttl}s)")
|
|
|
|
async def invalidate(self, pattern: str) -> int:
|
|
"""按模式清除缓存(支持 * 通配符)"""
|
|
async with self.lock:
|
|
if pattern == "*":
|
|
count = len(self.cache)
|
|
self.cache.clear()
|
|
logger.info(f"Cache cleared: {count} entries")
|
|
return count
|
|
|
|
keys_to_delete = [k for k in self.cache.keys() if fnmatch.fnmatch(k, pattern)]
|
|
for key in keys_to_delete:
|
|
del self.cache[key]
|
|
logger.info(f"Cache invalidated: {len(keys_to_delete)} entries matching '{pattern}'")
|
|
return len(keys_to_delete)
|
|
|
|
async def get_stats(self) -> dict:
|
|
"""获取缓存统计信息"""
|
|
async with self.lock:
|
|
now = time.time()
|
|
expired_count = sum(1 for _, expire_time in self.cache.values() if expire_time and now > expire_time)
|
|
|
|
return {
|
|
"size": len(self.cache),
|
|
"max_size": self.max_size,
|
|
"expired_count": expired_count,
|
|
"utilization": len(self.cache) / self.max_size if self.max_size > 0 else 0
|
|
}
|
|
|
|
async def clear(self) -> None:
|
|
"""清空缓存"""
|
|
async with self.lock:
|
|
self.cache.clear()
|
|
logger.info("Cache cleared")
|