Files
zhenxun_bot/plan.md
T

10 KiB
Raw Blame History

权限检查系统优化方案

项目概述

优化 zhenxun_bot 的权限检查系统,将每条消息的数据库/缓存查询次数从 6-10次 降低到 1-2次。


当前问题分析

现有查询流程

每条消息进入时,权限检查系统执行以下查询:

阶段 查询内容 次数
_load_context PluginInfo, UserConsole, GroupConsole 3次
auth_ban BanConsole 1-2次
auth_bot BotConsole 1次
auth_admin LevelUser (全局+群组) 1-2次
auth_limit PluginLimit (如果不在内存) 0-1次

总计:6-10次查询

问题根源

  1. 数据分散在多个表:user_console, group_console, ban_console, bot_console, level_user, plugin_info
  2. 每个检查模块独立查询,缺乏数据共享
  3. 即使有 Redis 缓存,也需要多次网络往返

优化方案:预聚合权限快照 (Permission Snapshot)

核心思想

用一个 Hash 结构存储权限检查所需的所有数据,消息到达时只需 1-2 次查询。

数据结构设计

1. 权限快照 (AuthSnapshot)

缓存键格式: AUTH_SNAPSHOT:{user_id}:{group_id}:{bot_id}

Hash 结构:
{
    # === 用户信息 ===
    "user_gold": 100,                  # 用户金币
    "user_banned": 0,                  # 0=未ban, -1=永久ban, >0=ban结束时间戳
    "user_ban_duration": 0,            # ban时长(用于计算剩余时间)
    
    # === 用户权限等级 ===
    "user_level_global": 0,            # 全局权限等级
    "user_level_group": 0,             # 群组权限等级
    
    # === 群组信息 ===
    "group_status": 1,                 # 群组状态 (1=开启, 0=休眠)
    "group_level": 5,                  # 群组等级
    "group_is_super": 0,               # 是否超级群组
    "group_block_plugins": "",         # 禁用插件列表 "<plugin1,<plugin2,"
    "group_superuser_block_plugins": "", # 超级用户禁用插件列表
    "group_banned": 0,                 # 群组是否被ban
    
    # === Bot信息 ===
    "bot_status": 1,                   # Bot状态
    "bot_block_plugins": "",           # Bot禁用插件列表
    
    # === 元数据 ===
    "version": 1,                      # 快照版本(用于失效判断)
    "created_at": 1703859600           # 创建时间戳
}

2. 插件信息缓存 (PluginSnapshot)

插件是全局的,变化较少,可以使用本地内存缓存 + Redis 双层缓存:

缓存键格式: PLUGIN_SNAPSHOT:{module}

结构:
{
    "status": true,                    # 全局开关状态
    "block_type": null,                # 禁用类型 (PRIVATE/GROUP/ALL/null)
    "admin_level": 0,                  # 调用所需权限等级
    "cost_gold": 0,                    # 调用所需金币
    "level": 5,                        # 所需群权限等级
    "limit_superuser": false,          # 是否限制超级用户
    "plugin_type": "NORMAL",           # 插件类型
    "ignore_prompt": false             # 是否忽略阻断提示
}

工作流程

消息到达
    │
    ▼
┌──────────────────────────────────────────────────────┐
│  1. 第一次查询:获取权限快照                           │
│     AUTH_SNAPSHOT:{user_id}:{group_id}:{bot_id}      │
│                                                      │
│     - 如果存在且未过期 → 直接使用                      │
│     - 如果不存在 → 触发快照构建(异步)                 │
└──────────────────────────────────────────────────────┘
    │
    ▼
┌──────────────────────────────────────────────────────┐
│  2. 第二次查询:获取插件信息                           │
│     PLUGIN_SNAPSHOT:{module}                          │
│                                                      │
│     - 优先从本地内存缓存获取                           │
│     - 未命中时从 Redis 获取                            │
│     - 仍未命中时从 DB 加载并缓存                        │
└──────────────────────────────────────────────────────┘
    │
    ▼
┌──────────────────────────────────────────────────────┐
│  3. 执行权限检查(纯内存计算,无 I/O)                  │
│                                                      │
│     - ban 检查                                        │
│     - bot 状态检查                                    │
│     - 插件状态检查                                    │
│     - 群组状态检查                                    │
│     - 权限等级检查                                    │
│     - 金币检查                                        │
└──────────────────────────────────────────────────────┘
    │
    ▼
 权限检查完成

缓存失效策略

主动失效(事件驱动)

事件 失效范围
用户金币变化 AUTH_SNAPSHOT:{user_id}:*:*
用户被 ban/unban AUTH_SNAPSHOT:{user_id}:*:*
群组设置变更 AUTH_SNAPSHOT:*:{group_id}:*
Bot 配置变更 AUTH_SNAPSHOT:*:*:{bot_id}
插件配置变更 PLUGIN_SNAPSHOT:{module} + 本地内存缓存
用户权限变更 AUTH_SNAPSHOT:{user_id}:{group_id}:*

被动失效(TTL)

  • 权限快照 TTL:60秒(权衡实时性和性能)
  • 插件快照 TTL:300秒(插件配置变化较少)
  • 本地内存缓存 TTL:30秒

实现计划

Phase 1: 基础设施 ✅ [已完成]

  • 新增 CacheType.AUTH_SNAPSHOT 和 CacheType.PLUGIN_SNAPSHOT
  • 创建 AuthSnapshot Pydantic 模型
  • 创建 PluginSnapshot Pydantic 模型
  • 实现快照构建器 SnapshotBuilder

Phase 2: 快照服务 ✅ [已完成]

  • 创建 AuthSnapshotService 类

    • get_snapshot(user_id, group_id, bot_id) - 获取权限快照
    • build_snapshot(user_id, group_id, bot_id) - 构建权限快照
    • invalidate_user(user_id) - 失效用户相关快照
    • invalidate_group(group_id) - 失效群组相关快照
    • invalidate_bot(bot_id) - 失效Bot相关快照
  • 创建 PluginSnapshotService 类

    • get_plugin(module) - 获取插件信息(本地缓存优先)
    • invalidate_plugin(module) - 失效插件缓存
    • warmup() - 预热所有插件缓存

Phase 3: 权限检查器重构 ✅ [已完成]

  • 创建新的 OptimizedAuthChecker 类
  • 基于快照数据的权限检查逻辑
  • 无 I/O 的纯内存计算
  • 保持与现有系统的兼容性

Phase 4: 缓存失效集成 ⏳ [可选优化]

注:当前实现使用 TTL 自动过期机制,以下为可选的主动失效优化

  • 在 UserConsole 的写操作中添加失效逻辑
  • 在 GroupConsole 的写操作中添加失效逻辑
  • 在 BanConsole 的写操作中添加失效逻辑
  • 在 BotConsole 的写操作中添加失效逻辑
  • 在 LevelUser 的写操作中添加失效逻辑
  • 在 PluginInfo 的写操作中添加失效逻辑

Phase 5: 测试与验证 ⏳ [待测试]

  • 单元测试
  • 性能对比测试
  • 边界情况测试

文件结构

zhenxun/
├── services/
│   └── auth_snapshot/
│       ├── __init__.py
│       ├── models.py          # AuthSnapshot, PluginSnapshot 模型
│       ├── builder.py         # 快照构建器
│       ├── service.py         # 快照服务
│       └── checker.py         # 优化后的权限检查器
└── builtin_plugins/
    └── hooks/
        └── auth_checker_v2.py # 新版权限检查入口

性能预期

指标 优化前 优化后 提升
查询次数 6-10次 1-2次 80%↓
平均延迟 ~50ms ~10ms 80%↓
Redis 连接压力 高 低 显著降低

风险与缓解

风险 缓解措施
快照数据过期 合理的 TTL + 主动失效机制
快照构建延迟 异步构建 + 首次访问降级到旧流程
内存占用增加 监控内存使用 + 合理的缓存清理
数据一致性 写操作后立即失效缓存


使用方式

方式一:替换原有权限检查器(推荐)

修改 zhenxun/builtin_plugins/hooks/__init__.py,将 auth_checker 替换为 auth_checker_v2:

# 原来的导入
# from . import auth_checker

# 替换为
from . import auth_checker_v2

方式二:并行测试

同时加载两个版本,通过日志对比性能:

from . import auth_checker      # 原版本
from . import auth_checker_v2   # 优化版本(会覆盖原版本的 run_preprocessor)

API 使用示例

from zhenxun.services.auth_snapshot import (
    AuthSnapshotService,
    PluginSnapshotService,
    AuthSnapshot,
    PluginSnapshot,
)

# 获取权限快照
snapshot = await AuthSnapshotService.get_snapshot(
    user_id="123456",
    group_id="789012",
    bot_id="bot_001"
)

# 检查用户是否被ban
if snapshot.is_user_banned():
    print(f"用户被ban,剩余时间: {snapshot.get_user_ban_remaining()}秒")

# 获取插件快照
plugin = await PluginSnapshotService.get_plugin("example_plugin")
if plugin and plugin.cost_gold > 0:
    print(f"此插件需要 {plugin.cost_gold} 金币")

# 手动失效缓存(数据更新时调用)
await AuthSnapshotService.invalidate_user("123456")
await PluginSnapshotService.invalidate_plugin("example_plugin")

进度追踪

  • 开始日期:2025-12-29
  • 当前阶段:核心功能已完成
  • 状态:✅ 基础功能完成,待测试验证