""" Service Layer - GraphService(图服务) 管理页面间的链接关系,查找孤立页面和缺失链接。 参考设计文档:第 2.3 节 """ import logging from typing import Set, List, Tuple from ..storage import Database logger = logging.getLogger(__name__) class GraphService: """图服务 - 管理链接关系""" def __init__(self, db: Database): self.db = db async def get_links(self, path: str) -> Set[str]: """获取页面的出链""" return await self.db.get_links(path) async def get_backlinks(self, path: str) -> Set[str]: """获取页面的反向链接""" return await self.db.get_backlinks(path) async def find_orphans(self) -> Set[str]: """查找孤立页面(无反向链接)""" 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) return orphans async def find_missing_links(self) -> List[Tuple[str, str]]: """查找缺失的交叉引用""" # 获取所有页面内容 all_pages = await self.db.get_all_pages() page_paths = {page.path for page in all_pages} # 获取所有链接关系 links_rows = await self.db.fetch_all("SELECT source, target FROM wiki_links") missing = [] for source, target in links_rows: if target not in page_paths: missing.append((source, target)) return missing async def get_link_graph(self) -> dict: """获取完整的链接图""" pages = await self.db.get_all_pages() graph = {} for page in pages: links = await self.db.get_links(page.path) backlinks = await self.db.get_backlinks(page.path) graph[page.path] = { "title": page.title, "category": page.category, "links": sorted(links), "backlinks": sorted(backlinks), "links_count": len(links), "backlinks_count": len(backlinks) } return graph async def get_highly_connected_pages(self, threshold: int = 5) -> List[dict]: """获取高度连接的页面(入链 + 出链 >= threshold)""" pages = await self.db.get_all_pages() highly_connected = [] for page in pages: links = await self.db.get_links(page.path) backlinks = await self.db.get_backlinks(page.path) total = len(links) + len(backlinks) if total >= threshold: highly_connected.append({ "path": page.path, "title": page.title, "links_count": len(links), "backlinks_count": len(backlinks), "total_connections": total }) return sorted(highly_connected, key=lambda x: x["total_connections"], reverse=True) async def get_disconnected_components(self) -> List[Set[str]]: """获取不连通的图分量(使用 BFS)""" pages = await self.db.get_all_pages() page_paths = {page.path for page in pages} if not page_paths: return [] visited = set() components = [] for start_path in page_paths: if start_path in visited: continue # BFS 构建连通分量 component = set() queue = [start_path] while queue: current = queue.pop(0) if current in visited: continue visited.add(current) component.add(current) # 添加出链和入链 for link in await self.db.get_links(current): if link in page_paths and link not in visited: queue.append(link) for backlink in await self.db.get_backlinks(current): if backlink in page_paths and backlink not in visited: queue.append(backlink) components.append(component) # 返回最大的分量以外的所有分量(即孤立的子图) main_component = max(components, key=len) return [c for c in components if c != main_component]