bugfix:修复命令前缀判断问题、修复超级用户无法豁免问题 (#2141)

* bugfix:修复命令前缀判断问题、修复超级用户无法豁免问题

* bugfix:修复格式问题
This commit is contained in:
Copaan
2026-06-16 17:14:03 +08:00
committed by GitHub
parent 8afc8f8673
commit a2c0cfdf5d
18 changed files with 224 additions and 51 deletions
+29 -9
View File
@@ -74,8 +74,12 @@ class BoundedTTLCache(Generic[K, V]):
self._sets = 0
self._evictions = 0
self._lock = asyncio.Lock()
self._last_sweep = 0.0
self.__class__._instances.add(self)
# 全量过期清理的最小间隔(B1):避免每次 get 都 O(N) 扫描整表。
_SWEEP_INTERVAL = 30.0
def _expire_at(self, now: float) -> float:
if self._ttl_seconds <= 0:
return sys.float_info.max
@@ -106,23 +110,37 @@ class BoundedTTLCache(Generic[K, V]):
self._evictions += 1
return True
def _cleanup_nolock(self, now: float) -> None:
expired_keys = [
key for key, (expire_at, _, _) in self._cache.items() if expire_at <= now
]
for key in expired_keys:
if self._remove_key_nolock(key):
self._evictions += 1
def _enforce_capacity_nolock(self) -> None:
"""容量边界强制(O(溢出量)):仅在新增后调用,不扫描全表。"""
while len(self._cache) > self._max_items:
self._pop_oldest_nolock()
if self._max_total_bytes is not None:
while self._total_bytes > self._max_total_bytes and self._cache:
self._pop_oldest_nolock()
def _sweep_expired_nolock(self, now: float) -> None:
"""全量过期清理(O(N)):由节流器或后台/governor 低频触发。"""
self._last_sweep = now
expired_keys = [
key for key, (expire_at, _, _) in self._cache.items() if expire_at <= now
]
for key in expired_keys:
if self._remove_key_nolock(key):
self._evictions += 1
def _maybe_sweep_nolock(self, now: float) -> None:
if now - self._last_sweep >= self._SWEEP_INTERVAL:
self._sweep_expired_nolock(now)
def _cleanup_nolock(self, now: float) -> None:
"""完整清理(全量过期 + 容量):保留给 stats / 显式调用。"""
self._sweep_expired_nolock(now)
self._enforce_capacity_nolock()
async def get(self, key: K) -> V | None:
now = time.monotonic()
async with self._lock:
self._cleanup_nolock(now)
# 仅做命中项的单条过期检查(O(1)),全量清理改为低频节流(B1)。
item = self._cache.get(key)
if item is None:
self._misses += 1
@@ -148,7 +166,9 @@ class BoundedTTLCache(Generic[K, V]):
self._total_bytes += value_size
self._sets += 1
self._cache.move_to_end(key)
self._cleanup_nolock(now)
# 新增后必做容量强制(廉价);全量过期清理走低频节流(B1)。
self._maybe_sweep_nolock(now)
self._enforce_capacity_nolock()
return key in self._cache
async def delete(self, key: K) -> bool:
+4
View File
@@ -146,6 +146,10 @@ class DataAccess(Generic[T]):
返回:
str | None: 缓存键,如果无法构建则返回None
"""
# 含 ORM lookup 后缀(如 channel_id__isnull / x__gte)的查询无法可靠映射到
# 单条主键缓存键,退化为直查 DB,避免空串与 NULL 语义混淆导致错误命中(A5)。
if any("__" in key for key in kwargs):
return None
if isinstance(self.key_field, tuple):
# 多字段主键
key_parts = []
+2
View File
@@ -278,6 +278,7 @@ async def _execute_single_job_instance(
schedule_id=schedule.id,
plugin_name=plugin_name,
bot_id=bot.self_id,
platform_scope=PlatformUtils.get_platform_scope(bot),
group_id=group_id,
job_kwargs=schedule.job_kwargs if isinstance(schedule.job_kwargs, dict) else {},
)
@@ -381,6 +382,7 @@ async def _execute_job(
)
if bot is None:
return
context_override.platform_scope = PlatformUtils.get_platform_scope(bot)
logger.info(f"开始执行临时任务: {plugin_name}")
injected_params = {"context": context_override}
state: T_State = {ScheduleContext: context_override}
+1
View File
@@ -82,6 +82,7 @@ async def _load_schedules_from_db():
schedule_id=0,
plugin_name=job_id,
bot_id=None,
platform_scope=None,
group_id=None,
job_kwargs={},
)
+1
View File
@@ -250,6 +250,7 @@ class SchedulerManager:
schedule_id=0,
plugin_name=f"runtime::{func.__module__}",
bot_id=None,
platform_scope=None,
group_id=None,
job_kwargs={},
)
+1
View File
@@ -105,6 +105,7 @@ class ScheduleContext(BaseModel):
schedule_id: int = Field(..., description="数据库中的任务ID")
plugin_name: str = Field(..., description="任务所属的插件名称")
bot_id: str | None = Field(None, description="执行任务的Bot ID")
platform_scope: str | None = Field(None, description="执行任务的细粒度平台作用域")
group_id: str | None = Field(None, description="当前执行实例的目标群组ID")
job_kwargs: dict = Field(default_factory=dict, description="任务配置的参数")