Files
sanguo_llmwiki/mcp_server/services/query.py
T
claude_dev dfd8421dc6 feat(§03): 实现 MCP Server 核心 - Storage/Service/Tool/MCP Protocol Layers
**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>
2026-06-26 11:32:43 +08:00

121 lines
3.7 KiB
Python

"""
Service Layer - QueryService(查询服务)
负责所有查询逻辑,包括 FTS5 全文搜索、标签搜索、页面查询等。
参考设计文档:第 2.3 节
"""
import logging
from typing import List, Set, Optional
from ..storage import Database, WikiPage
from .cache import CacheService
logger = logging.getLogger(__name__)
class QueryService:
"""查询服务 - 负责所有查询逻辑"""
def __init__(self, db: Database, cache: CacheService):
self.db = db
self.cache = cache
async def search(self, query: str, limit: int = 10) -> List[WikiPage]:
"""FTS5 全文搜索"""
# 1. 检查缓存
cache_key = f"search:{query}:{limit}"
cached = await self.cache.get(cache_key)
if cached:
logger.debug(f"Cache hit for search: {query}")
return cached
# 2. FTS5 搜索
results = await self.db.fts_search(query, limit)
# 3. 缓存结果
await self.cache.set(cache_key, results, ttl=3600)
return results
async def search_by_tags(self, tags: List[str]) -> List[WikiPage]:
"""按标签搜索"""
cache_key = f"tags:{','.join(tags)}"
cached = await self.cache.get(cache_key)
if cached:
return cached
results = await self.db.search_by_tags(tags)
await self.cache.set(cache_key, results, ttl=3600)
return results
async def search_by_source_tool(self, tool_name: str, limit: int = 50) -> List[WikiPage]:
"""按来源工具搜索(memory_bridge 使用)"""
cache_key = f"tool:{tool_name}:{limit}"
cached = await self.cache.get(cache_key)
if cached:
return cached
results = await self.db.search_by_source_tool(tool_name, limit)
await self.cache.set(cache_key, results, ttl=1800) # 30 分钟 TTL
return results
async def get_page(self, path: str) -> Optional[WikiPage]:
"""获取单个页面"""
cache_key = f"page:{path}"
cached = await self.cache.get(cache_key)
if cached:
return cached
page = await self.db.get_page(path)
if page:
await self.cache.set(cache_key, page, ttl=3600)
return page
async def get_links(self, path: str) -> Set[str]:
"""获取页面的出链"""
cache_key = f"links:{path}"
cached = await self.cache.get(cache_key)
if cached:
return cached
links = await self.db.get_links(path)
await self.cache.set(cache_key, links, ttl=3600)
return links
async def get_backlinks(self, path: str) -> Set[str]:
"""获取页面的反向链接"""
cache_key = f"backlinks:{path}"
cached = await self.cache.get(cache_key)
if cached:
return cached
backlinks = await self.db.get_backlinks(path)
await self.cache.set(cache_key, backlinks, ttl=3600)
return backlinks
async def find_orphans(self) -> Set[str]:
"""查找孤立页面(无反向链接)"""
cache_key = "orphans"
cached = await self.cache.get(cache_key)
if cached:
return cached
all_pages = await self.db.get_all_pages()
orphans = set()
for page in all_pages:
backlinks = await self.db.get_backlinks(page.path)
if not backlinks and page.path != "index.md":
orphans.add(page.path)
await self.cache.set(cache_key, orphans, ttl=1800)
return orphans
async def get_stats(self) -> dict:
"""获取索引统计信息"""
return await self.db.get_stats()
async def invalidate_cache(self, pattern: str = "*") -> int:
"""使缓存失效"""
return await self.cache.invalidate(pattern)