fix: stabilize rule config and cross-review backend

This commit is contained in:
wren
2026-05-11 02:03:01 +08:00
parent 900fc2e8a2
commit 32fb2a4812
14 changed files with 444 additions and 46 deletions
@@ -5,6 +5,7 @@ from __future__ import annotations
import asyncio
from collections import defaultdict
import re
import time
from typing import Any
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
@@ -20,17 +21,49 @@ from fastapi_modules.fastapi_leaudit.domian.vo.ruleConfigVo import (
from fastapi_modules.fastapi_leaudit.leaudit_bridge.ruleValidator import RuleValidator
from fastapi_modules.fastapi_leaudit.services import IOssService
from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServiceImpl
from fastapi_modules.fastapi_leaudit.services.impl.ruleServiceImpl import RuleServiceImpl
from fastapi_modules.fastapi_leaudit.services.impl.ruleServiceImpl import GetRuleServiceSingleton
from fastapi_modules.fastapi_leaudit.services.ruleConfigService import IRuleConfigService
class RuleConfigServiceImpl(IRuleConfigService):
"""规则配置页聚合服务实现。"""
_GLOBAL_YAML_SUMMARY_CACHE: dict[int, tuple[float, dict[str, Any]]] = {}
_GLOBAL_PACK_SUMMARY_CACHE: tuple[float, list[RuleConfigPackListVO]] | None = None
_GLOBAL_WARM_LOCK: asyncio.Lock | None = None
def __init__(self, OssService: IOssService | None = None) -> None:
self.OssService = OssService or OssServiceImpl()
self.RuleService = RuleServiceImpl(self.OssService)
self.RuleService = GetRuleServiceSingleton()
self.Validator = RuleValidator()
self._yaml_summary_cache = self.__class__._GLOBAL_YAML_SUMMARY_CACHE
self._pack_summary_cache = self.__class__._GLOBAL_PACK_SUMMARY_CACHE
@classmethod
def _set_pack_summary_cache(cls, value: tuple[float, list[RuleConfigPackListVO]] | None) -> None:
cls._GLOBAL_PACK_SUMMARY_CACHE = value
@classmethod
def _get_warm_lock(cls) -> asyncio.Lock:
if cls._GLOBAL_WARM_LOCK is None:
cls._GLOBAL_WARM_LOCK = asyncio.Lock()
return cls._GLOBAL_WARM_LOCK
def InvalidateSummaryCaches(self, version_ids: list[int] | None = None) -> None:
"""清理规则摘要缓存;version_ids 为空时清空全部。"""
self.__class__._set_pack_summary_cache(None)
if version_ids is None:
self._yaml_summary_cache.clear()
return
for version_id in {int(item) for item in version_ids if item is not None}:
self._yaml_summary_cache.pop(version_id, None)
async def WarmPackSummaries(self, force: bool = False) -> list[RuleConfigPackListVO]:
"""预热规则列表摘要缓存。"""
async with self.__class__._get_warm_lock():
if force:
self.InvalidateSummaryCaches()
return await self.ListPackSummaries()
async def ListPacks(self) -> list[RuleConfigPackVO]:
"""列出规则配置页所需的全部 pack。"""
@@ -40,6 +73,11 @@ class RuleConfigServiceImpl(IRuleConfigService):
async def ListPackSummaries(self) -> list[RuleConfigPackListVO]:
"""列出规则列表页所需的轻量 pack。"""
cached = self.__class__._GLOBAL_PACK_SUMMARY_CACHE
now = time.monotonic()
if cached and now - cached[0] <= 60:
return cached[1]
rows = await self._load_pack_rows()
if not rows:
return []
@@ -121,6 +159,9 @@ class RuleConfigServiceImpl(IRuleConfigService):
item["usableRuleCount"] = len(rules)
packs.append(RuleConfigPackListVO(**item))
cache_value = (time.monotonic(), packs)
self.__class__._set_pack_summary_cache(cache_value)
self._pack_summary_cache = cache_value
return packs
async def GetPack(self, PackId: int) -> RuleConfigPackVO:
@@ -471,10 +512,16 @@ class RuleConfigServiceImpl(IRuleConfigService):
async def _load_one(version_id: int, oss_url: str):
if not oss_url:
return version_id, {"loaded": False, "yaml_name": "", "rules": []}
cached = self._yaml_summary_cache.get(version_id)
now = time.monotonic()
if cached and now - cached[0] <= 120:
return version_id, cached[1]
try:
yaml_text = (await self.OssService.DownloadBytes(oss_url)).decode("utf-8")
if not yaml_text.strip():
return version_id, {"loaded": False, "yaml_name": "", "rules": []}
summary = {"loaded": False, "yaml_name": "", "rules": []}
self._yaml_summary_cache[version_id] = (time.monotonic(), summary)
return version_id, summary
rules_file = self.Validator.ParseValidated(yaml_text)
groups: dict[str, str] = {}
try:
@@ -540,13 +587,17 @@ class RuleConfigServiceImpl(IRuleConfigService):
seen_dependencies.add(normalized_dependency)
merged_dependencies.append(normalized_dependency)
item["dependencies"] = merged_dependencies
return version_id, {
summary = {
"loaded": True,
"yaml_name": str(getattr(getattr(rules_file, "metadata", None), "name", "") or ""),
"rules": summaries,
}
self._yaml_summary_cache[version_id] = (time.monotonic(), summary)
return version_id, summary
except Exception:
return version_id, {"loaded": False, "yaml_name": "", "rules": []}
summary = {"loaded": False, "yaml_name": "", "rules": []}
self._yaml_summary_cache[version_id] = (time.monotonic(), summary)
return version_id, summary
results = await asyncio.gather(*[_load_one(version_id, oss_url) for version_id, oss_url in version_oss_map.items()])
return dict(results)
@@ -592,3 +643,14 @@ class RuleConfigServiceImpl(IRuleConfigService):
if value is None:
return None
return int(value)
_RULE_CONFIG_SERVICE_SINGLETON: RuleConfigServiceImpl | None = None
def GetRuleConfigServiceSingleton() -> RuleConfigServiceImpl:
"""返回共享的规则配置服务实例,供控制器和预热任务共用缓存。"""
global _RULE_CONFIG_SERVICE_SINGLETON
if _RULE_CONFIG_SERVICE_SINGLETON is None:
_RULE_CONFIG_SERVICE_SINGLETON = RuleConfigServiceImpl()
return _RULE_CONFIG_SERVICE_SINGLETON