mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 00:32:06 +08:00
refactor: align auth checker optimization with DataAccess caching
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
# 权限检查系统 - 缓存优化指南
|
||||
|
||||
## DataAccess 缓存机制说明
|
||||
|
||||
### 已实现的缓存功能
|
||||
|
||||
`DataAccess` 类已经实现了完整的 Redis 缓存机制,包括:
|
||||
|
||||
1. **自动缓存查询结果**
|
||||
```python
|
||||
# 第一次查询:从数据库获取并缓存
|
||||
plugin = await plugin_dao.safe_get_or_none(module="example")
|
||||
|
||||
# 后续查询:直接从缓存获取(如果缓存未过期)
|
||||
plugin = await plugin_dao.safe_get_or_none(module="example")
|
||||
```
|
||||
|
||||
2. **空结果缓存(5分钟TTL)**
|
||||
```python
|
||||
# 查询不存在的记录时,会缓存空结果标记
|
||||
# 避免频繁查询数据库确认记录不存在
|
||||
user = await user_dao.safe_get_or_none(user_id="nonexistent")
|
||||
# 5分钟内再次查询相同记录,直接从缓存返回 None
|
||||
```
|
||||
|
||||
3. **缓存统计**
|
||||
```python
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
|
||||
# 获取缓存统计信息
|
||||
stats = DataAccess.get_cache_stats()
|
||||
# 返回格式:
|
||||
# [
|
||||
# {
|
||||
# "cache_type": "GLOBAL_ALL_PLUGINS",
|
||||
# "hits": 1000, # 缓存命中次数
|
||||
# "null_hits": 50, # 空结果缓存命中次数
|
||||
# "misses": 200, # 缓存未命中次数
|
||||
# "hit_rate": "84.00%" # 缓存命中率
|
||||
# },
|
||||
# ...
|
||||
# ]
|
||||
```
|
||||
|
||||
4. **自动缓存失效**
|
||||
- 数据创建、更新、删除时自动清除相关缓存
|
||||
- 支持手动清除:`await dao.clear_cache(**kwargs)`
|
||||
|
||||
## 权限检查中的缓存优化
|
||||
|
||||
### 当前缓存使用情况
|
||||
|
||||
在权限检查系统中,以下数据访问已经自动使用缓存:
|
||||
|
||||
1. **PluginInfo(插件信息)**
|
||||
```python
|
||||
plugin_dao = DataAccess(PluginInfo)
|
||||
plugin = await plugin_dao.safe_get_or_none(module=module)
|
||||
# ✅ 自动缓存,基于 module 字段
|
||||
```
|
||||
|
||||
2. **UserConsole(用户信息)**
|
||||
```python
|
||||
user_dao = DataAccess(UserConsole)
|
||||
user = await user_dao.get_by_func_or_none(
|
||||
UserConsole.get_user, False, user_id=user_id
|
||||
)
|
||||
# ✅ 自动缓存,基于 user_id 字段
|
||||
```
|
||||
|
||||
3. **GroupConsole(群组信息)**
|
||||
```python
|
||||
group_dao = DataAccess(GroupConsole)
|
||||
group = await group_dao.safe_get_or_none(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
# ✅ 自动缓存,基于 group_id 字段
|
||||
```
|
||||
|
||||
4. **BanConsole(ban记录)**
|
||||
```python
|
||||
ban_dao = DataAccess(BanConsole)
|
||||
ban = await ban_dao.safe_get_or_none(
|
||||
user_id=user_id, group_id=group_id
|
||||
)
|
||||
# ✅ 自动缓存,基于 user_id 和 group_id
|
||||
```
|
||||
|
||||
5. **BotConsole(Bot信息)**
|
||||
```python
|
||||
bot_dao = DataAccess(BotConsole)
|
||||
bot = await bot_dao.safe_get_or_none(bot_id=bot_id)
|
||||
# ✅ 自动缓存,基于 bot_id 字段
|
||||
```
|
||||
|
||||
6. **LevelUser(用户权限)**
|
||||
```python
|
||||
level_dao = DataAccess(LevelUser)
|
||||
level = await level_dao.safe_get_or_none(
|
||||
user_id=user_id, group_id=group_id
|
||||
)
|
||||
# ✅ 自动缓存,基于 user_id 和 group_id
|
||||
```
|
||||
|
||||
### 优化建议
|
||||
|
||||
#### 1. 并行获取数据,提高缓存命中率
|
||||
|
||||
**当前实现**:
|
||||
```python
|
||||
# 串行获取,可能错过缓存优势
|
||||
plugin = await plugin_dao.safe_get_or_none(module=module)
|
||||
user = await user_dao.get_by_func_or_none(...)
|
||||
group = await group_dao.safe_get_or_none(...)
|
||||
```
|
||||
|
||||
**优化后**:
|
||||
```python
|
||||
# 并行获取,DataAccess 会自动处理缓存
|
||||
plugin, user, group = await asyncio.gather(
|
||||
plugin_dao.safe_get_or_none(module=module),
|
||||
user_dao.get_by_func_or_none(UserConsole.get_user, False, user_id=user_id),
|
||||
group_dao.safe_get_or_none(group_id=group_id, channel_id__isnull=True)
|
||||
if group_id else asyncio.sleep(0)
|
||||
)
|
||||
```
|
||||
|
||||
#### 2. 提前获取所有需要的数据
|
||||
|
||||
**当前实现**:
|
||||
```python
|
||||
# group 数据在 hooks 执行时才获取
|
||||
# 在 auth 函数中
|
||||
group = None
|
||||
if entity.group_id:
|
||||
group = await group_dao.safe_get_or_none(...)
|
||||
```
|
||||
|
||||
**优化后**:
|
||||
```python
|
||||
# 在获取 plugin 和 user 时,同时获取 group
|
||||
# 统一数据获取,充分利用缓存
|
||||
async def get_all_auth_data(module, user_id, group_id):
|
||||
plugin_dao = DataAccess(PluginInfo)
|
||||
user_dao = DataAccess(UserConsole)
|
||||
group_dao = DataAccess(GroupConsole) if group_id else None
|
||||
|
||||
tasks = [
|
||||
plugin_dao.safe_get_or_none(module=module),
|
||||
user_dao.get_by_func_or_none(UserConsole.get_user, False, user_id=user_id),
|
||||
]
|
||||
if group_id and group_dao:
|
||||
tasks.append(
|
||||
group_dao.safe_get_or_none(group_id=group_id, channel_id__isnull=True)
|
||||
)
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
return results
|
||||
```
|
||||
|
||||
#### 3. 监控缓存效果
|
||||
|
||||
**实现方案**:
|
||||
```python
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
|
||||
async def auth(...):
|
||||
# 记录开始时的缓存统计
|
||||
cache_stats_before = DataAccess.get_cache_stats()
|
||||
|
||||
try:
|
||||
# 执行权限检查
|
||||
...
|
||||
finally:
|
||||
# 记录结束时的缓存统计
|
||||
cache_stats_after = DataAccess.get_cache_stats()
|
||||
|
||||
# 分析缓存效果
|
||||
for before, after in zip(cache_stats_before, cache_stats_after):
|
||||
cache_type = after["cache_type"]
|
||||
hits_diff = after["hits"] - before["hits"]
|
||||
misses_diff = after["misses"] - before["misses"]
|
||||
total = hits_diff + misses_diff
|
||||
|
||||
if total > 0:
|
||||
hit_rate = (hits_diff / total) * 100
|
||||
logger.debug(
|
||||
f"缓存统计 - {cache_type}: "
|
||||
f"命中率={hit_rate:.1f}%, "
|
||||
f"命中={hits_diff}, 未命中={misses_diff}",
|
||||
LOGGER_COMMAND
|
||||
)
|
||||
|
||||
# 如果缓存命中率过低,记录警告
|
||||
if hit_rate < 30:
|
||||
logger.warning(
|
||||
f"缓存命中率过低: {cache_type} = {hit_rate:.1f}%",
|
||||
LOGGER_COMMAND
|
||||
)
|
||||
```
|
||||
|
||||
#### 4. 调整空结果缓存时间
|
||||
|
||||
**根据实际需求调整**:
|
||||
```python
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
|
||||
# 对于频繁查询但可能不存在的记录,可以增加空结果缓存时间
|
||||
# 例如:ban记录查询,如果用户未被ban,可以缓存更长时间
|
||||
DataAccess.set_null_result_ttl(600) # 10分钟
|
||||
|
||||
# 对于需要实时性的数据,可以减少缓存时间
|
||||
# 例如:用户权限查询
|
||||
DataAccess.set_null_result_ttl(60) # 1分钟
|
||||
```
|
||||
|
||||
## 缓存性能优化检查清单
|
||||
|
||||
- [ ] 所有数据访问都通过 `DataAccess`,而不是直接使用模型类
|
||||
- [ ] 并行获取多个数据,而不是串行获取
|
||||
- [ ] 提前获取所有需要的数据,避免在检查过程中重复获取
|
||||
- [ ] 监控缓存命中率,确保缓存效果良好
|
||||
- [ ] 根据实际需求调整空结果缓存时间
|
||||
- [ ] 定期查看缓存统计,优化缓存策略
|
||||
|
||||
## 预期缓存效果
|
||||
|
||||
### 高并发场景下的缓存命中率预期
|
||||
|
||||
- **PluginInfo(插件信息)**:90-95%(插件信息变化不频繁)
|
||||
- **UserConsole(用户信息)**:80-90%(用户信息相对稳定)
|
||||
- **GroupConsole(群组信息)**:85-95%(群组信息变化不频繁)
|
||||
- **BanConsole(ban记录)**:70-85%(ban状态可能变化)
|
||||
- **BotConsole(Bot信息)**:95-99%(Bot信息很少变化)
|
||||
- **LevelUser(用户权限)**:75-85%(权限可能变化)
|
||||
|
||||
### 性能提升预期
|
||||
|
||||
- **缓存命中时**:查询时间从 10-50ms 降低到 1-5ms(减少 80-90%)
|
||||
- **总体性能**:在高并发场景下,总体执行时间减少 30-50%
|
||||
- **数据库压力**:减少数据库查询 40-60%
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **缓存一致性**:DataAccess 在数据更新时会自动清除缓存,确保一致性
|
||||
2. **缓存失效**:如果需要强制刷新缓存,可以使用 `await dao.clear_cache(**kwargs)`
|
||||
3. **监控缓存**:定期查看缓存统计,确保缓存效果良好
|
||||
4. **空结果缓存**:空结果缓存有助于减少对不存在记录的查询,但需要根据实际需求调整TTL
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
# 权限检查系统 - 立即可以实施的改进
|
||||
|
||||
## 当前问题总结
|
||||
|
||||
经过分析,当前权限检查系统存在以下主要问题:
|
||||
|
||||
### 1. 检查执行顺序不合理
|
||||
|
||||
- **问题**:所有检查并行执行,ban 检查应该优先执行
|
||||
- **影响**:用户被 ban 后,其他检查(金币、限制等)仍然会执行,浪费资源
|
||||
- **优先级**:高
|
||||
|
||||
### 2. 缺乏早期退出机制
|
||||
|
||||
- **问题**:即使某个检查失败,其他检查仍会继续执行
|
||||
- **影响**:浪费 CPU 和数据库资源
|
||||
- **优先级**:高
|
||||
|
||||
### 3. 数据获取可以优化
|
||||
|
||||
- **问题**:group 数据在 hooks 执行时才获取,可以提前获取
|
||||
- **影响**:增加总体执行时间
|
||||
- **优先级**:中
|
||||
|
||||
### 4. 充分利用 DataAccess 的 Redis 缓存
|
||||
|
||||
- **现状**:`DataAccess` 已经实现了完整的 Redis 缓存机制(包括空结果缓存 5 分钟)
|
||||
- **问题**:没有充分利用缓存,数据获取顺序不够优化
|
||||
- **影响**:缓存命中率可能不够高,重复查询数据库
|
||||
- **优先级**:高
|
||||
|
||||
## 立即可以实施的改进
|
||||
|
||||
### 改进 1:调整检查顺序,ban 检查优先执行
|
||||
|
||||
**位置**:`auth_checker.py` 第 498-514 行
|
||||
|
||||
**当前代码**:
|
||||
|
||||
```python
|
||||
hook_tasks = [
|
||||
time_hook(auth_ban(...), "auth_ban", hook_times),
|
||||
time_hook(auth_bot(...), "auth_bot", hook_times),
|
||||
# ... 其他检查
|
||||
]
|
||||
await asyncio.gather(*hook_tasks) # 并行执行
|
||||
```
|
||||
|
||||
**改进方案**:
|
||||
|
||||
```python
|
||||
# 1. 先执行关键检查(ban、bot),如果失败立即退出
|
||||
critical_checks = [
|
||||
("auth_ban", lambda: time_hook(auth_ban(...), "auth_ban", hook_times)),
|
||||
("auth_bot", lambda: time_hook(auth_bot(...), "auth_bot", hook_times)),
|
||||
]
|
||||
|
||||
for check_name, check_func in critical_checks:
|
||||
try:
|
||||
await check_func()
|
||||
except SkipPluginException:
|
||||
raise # 立即退出
|
||||
except Exception as e:
|
||||
logger.error(f"{check_name} 检查失败: {e}", LOGGER_COMMAND, session=session)
|
||||
# 关键检查失败,可以选择继续或退出
|
||||
|
||||
# 2. 关键检查通过后,再并行执行其他检查
|
||||
other_checks = [
|
||||
time_hook(auth_group(...), "auth_group", hook_times),
|
||||
time_hook(auth_admin(...), "auth_admin", hook_times),
|
||||
time_hook(auth_plugin(...), "auth_plugin", hook_times),
|
||||
time_hook(auth_limit(...), "auth_limit", hook_times),
|
||||
]
|
||||
await asyncio.gather(*other_checks)
|
||||
```
|
||||
|
||||
### 改进 2:提前获取 group 数据
|
||||
|
||||
**位置**:`auth_checker.py` 第 485-493 行
|
||||
|
||||
**当前代码**:
|
||||
|
||||
```python
|
||||
# 在hooks执行时才获取group
|
||||
group = None
|
||||
if entity.group_id:
|
||||
group_dao = DataAccess(GroupConsole)
|
||||
group = await with_timeout(...)
|
||||
```
|
||||
|
||||
**改进方案**:
|
||||
|
||||
```python
|
||||
# 在获取plugin和user时,同时获取group
|
||||
# 在 get_plugin_and_user 函数中或auth函数开始处
|
||||
group = None
|
||||
if entity.group_id:
|
||||
group_dao = DataAccess(GroupConsole)
|
||||
group_task = group_dao.safe_get_or_none(
|
||||
group_id=entity.group_id, channel_id__isnull=True
|
||||
)
|
||||
# 可以并行获取
|
||||
plugin, user, group = await asyncio.gather(
|
||||
plugin_task, user_task, group_task
|
||||
)
|
||||
```
|
||||
|
||||
### 改进 3:充分利用 DataAccess 的 Redis 缓存机制
|
||||
|
||||
**重要说明**:`DataAccess` 已经实现了完整的 Redis 缓存机制,包括:
|
||||
|
||||
- ✅ 自动缓存查询结果
|
||||
- ✅ 空结果缓存(5 分钟 TTL,避免频繁查询不存在的记录)
|
||||
- ✅ 缓存统计(命中率、命中次数等)
|
||||
- ✅ 自动处理缓存的获取和设置
|
||||
|
||||
**优化策略**:
|
||||
|
||||
#### 3.1 优化数据获取顺序,提高缓存命中率
|
||||
|
||||
**位置**:`auth_checker.py` 的 `get_plugin_and_user` 函数和 `auth` 函数
|
||||
|
||||
**当前问题**:
|
||||
|
||||
- plugin、user、group 数据分别获取,可能错过并行获取的缓存优势
|
||||
- group 数据在 hooks 执行时才获取,应该提前获取
|
||||
|
||||
**改进方案**:
|
||||
|
||||
```python
|
||||
async def get_plugin_user_and_group(
|
||||
module: str, user_id: str, group_id: str | None
|
||||
) -> tuple[PluginInfo, UserConsole, GroupConsole | None]:
|
||||
"""统一获取插件、用户和群组数据(充分利用 DataAccess 缓存)
|
||||
|
||||
注意:DataAccess 会自动使用 Redis 缓存,包括:
|
||||
- 查询结果缓存
|
||||
- 空结果缓存(5分钟)
|
||||
- 缓存统计
|
||||
"""
|
||||
user_dao = DataAccess(UserConsole)
|
||||
plugin_dao = DataAccess(PluginInfo)
|
||||
group_dao = DataAccess(GroupConsole) if group_id else None
|
||||
|
||||
# 并行获取所有数据,DataAccess 会自动处理缓存
|
||||
tasks = [
|
||||
plugin_dao.safe_get_or_none(module=module),
|
||||
user_dao.get_by_func_or_none(
|
||||
UserConsole.get_user, False, user_id=user_id
|
||||
)
|
||||
]
|
||||
|
||||
if group_id and group_dao:
|
||||
tasks.append(
|
||||
group_dao.safe_get_or_none(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
)
|
||||
else:
|
||||
tasks.append(asyncio.create_task(asyncio.sleep(0))) # 占位
|
||||
|
||||
try:
|
||||
results = await with_timeout(
|
||||
asyncio.gather(*tasks), name="get_plugin_user_group"
|
||||
)
|
||||
plugin = results[0]
|
||||
user = results[1]
|
||||
group = results[2] if len(results) > 2 and group_id else None
|
||||
|
||||
# 验证数据
|
||||
if not plugin:
|
||||
raise PermissionExemption(f"插件:{module} 数据不存在...")
|
||||
if plugin.plugin_type == PluginType.HIDDEN:
|
||||
raise PermissionExemption(f"插件: {plugin.name}:{plugin.module} 为HIDDEN...")
|
||||
if not user:
|
||||
raise PermissionExemption("用户数据不存在...")
|
||||
|
||||
return plugin, user, group
|
||||
except asyncio.TimeoutError:
|
||||
# 超时时尝试从缓存获取(DataAccess 已经缓存了)
|
||||
logger.warning(
|
||||
f"数据获取超时,尝试从缓存获取,模块: {module}",
|
||||
LOGGER_COMMAND
|
||||
)
|
||||
# DataAccess 的缓存会自动生效,这里只需要重试一次
|
||||
plugin = await plugin_dao.safe_get_or_none(module=module)
|
||||
user = await user_dao.get_by_func_or_none(
|
||||
UserConsole.get_user, False, user_id=user_id
|
||||
)
|
||||
group = None
|
||||
if group_id and group_dao:
|
||||
group = await group_dao.safe_get_or_none(
|
||||
group_id=group_id, channel_id__isnull=True
|
||||
)
|
||||
|
||||
if not plugin or not user:
|
||||
raise PermissionExemption("获取数据失败,请稍后再试...")
|
||||
|
||||
return plugin, user, group
|
||||
```
|
||||
|
||||
#### 3.2 监控缓存命中率
|
||||
|
||||
**位置**:在权限检查中添加缓存统计监控
|
||||
|
||||
**实现方案**:
|
||||
|
||||
```python
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
|
||||
# 在 auth 函数开始和结束时记录缓存统计
|
||||
async def auth(...):
|
||||
# 记录开始时的缓存统计
|
||||
cache_stats_before = DataAccess.get_cache_stats()
|
||||
|
||||
try:
|
||||
# ... 执行权限检查 ...
|
||||
pass
|
||||
finally:
|
||||
# 记录结束时的缓存统计
|
||||
cache_stats_after = DataAccess.get_cache_stats()
|
||||
|
||||
# 计算本次检查的缓存命中情况
|
||||
for before, after in zip(cache_stats_before, cache_stats_after):
|
||||
hits_diff = after["hits"] - before["hits"]
|
||||
misses_diff = after["misses"] - before["misses"]
|
||||
total = hits_diff + misses_diff
|
||||
if total > 0:
|
||||
hit_rate = (hits_diff / total) * 100
|
||||
if hit_rate < 50: # 缓存命中率低于50%时记录警告
|
||||
logger.warning(
|
||||
f"缓存命中率较低: {after['cache_type']} = {hit_rate:.1f}%",
|
||||
LOGGER_COMMAND
|
||||
)
|
||||
```
|
||||
|
||||
#### 3.3 优化检查函数中的数据访问
|
||||
|
||||
**说明**:各个检查函数(auth_ban、auth_bot 等)中使用的 `DataAccess` 已经自动缓存,无需额外处理。
|
||||
|
||||
**建议**:
|
||||
|
||||
- 确保所有数据访问都通过 `DataAccess`,而不是直接使用模型类
|
||||
- 对于频繁查询的数据(如 ban 记录),`DataAccess` 会自动缓存
|
||||
- 空结果也会被缓存 5 分钟,避免频繁查询不存在的记录
|
||||
|
||||
### 改进 4:统一错误处理策略
|
||||
|
||||
**位置**:各个检查函数
|
||||
|
||||
**当前问题**:
|
||||
|
||||
- `auth_ban` 超时时不阻塞
|
||||
- `auth_bot` 超时时不阻塞
|
||||
- `auth_limit` 超时时不阻塞
|
||||
- 但 `get_plugin_and_user` 超时时会抛出异常
|
||||
|
||||
**改进方案**:
|
||||
|
||||
```python
|
||||
# 定义统一的超时处理策略
|
||||
class TimeoutStrategy:
|
||||
FAIL_FAST = "fail_fast" # 超时立即失败
|
||||
CONTINUE = "continue" # 超时继续执行
|
||||
CACHE_FALLBACK = "cache_fallback" # 超时使用缓存
|
||||
|
||||
# 根据检查的重要性设置策略
|
||||
CHECK_TIMEOUT_STRATEGY = {
|
||||
"auth_ban": TimeoutStrategy.FAIL_FAST, # 关键检查,超时失败
|
||||
"auth_bot": TimeoutStrategy.FAIL_FAST, # 关键检查,超时失败
|
||||
"auth_group": TimeoutStrategy.CONTINUE, # 非关键,超时继续
|
||||
"auth_limit": TimeoutStrategy.CONTINUE, # 非关键,超时继续
|
||||
"get_plugin_and_user": TimeoutStrategy.CACHE_FALLBACK, # 使用缓存降级
|
||||
}
|
||||
```
|
||||
|
||||
## 实施优先级
|
||||
|
||||
### 高优先级(立即实施)
|
||||
|
||||
1. ✅ **调整检查顺序**:ban 检查优先执行
|
||||
- 风险:低
|
||||
- 收益:高
|
||||
- 工作量:小(1-2 小时)
|
||||
|
||||
### 中优先级(本周实施)
|
||||
|
||||
2. ✅ **提前获取 group 数据,统一数据获取**
|
||||
|
||||
- 风险:低
|
||||
- 收益:高(充分利用 DataAccess 缓存)
|
||||
- 工作量:小(1-2 小时)
|
||||
|
||||
3. ✅ **优化数据获取顺序,提高缓存命中率**
|
||||
|
||||
- 风险:低(DataAccess 已实现缓存)
|
||||
- 收益:高(减少数据库查询)
|
||||
- 工作量:小(1 小时)
|
||||
|
||||
4. ✅ **添加缓存统计监控**
|
||||
- 风险:低
|
||||
- 收益:中(可以监控缓存效果)
|
||||
- 工作量:小(30 分钟)
|
||||
|
||||
### 低优先级(后续优化)
|
||||
|
||||
4. ⏳ **统一错误处理策略**
|
||||
- 风险:中(可能影响现有逻辑)
|
||||
- 收益:中
|
||||
- 工作量:中(3-4 小时)
|
||||
|
||||
## 代码示例:改进后的 auth 函数结构
|
||||
|
||||
```python
|
||||
async def auth(...):
|
||||
# 1. 获取所有需要的数据(并行)
|
||||
plugin, user, group = await gather_all_data(...)
|
||||
|
||||
# 2. 关键检查(串行,失败立即退出)
|
||||
await critical_checks(plugin, user, group, ...)
|
||||
|
||||
# 3. 其他检查(并行)
|
||||
await other_checks(plugin, user, group, ...)
|
||||
|
||||
# 4. 金币检查(最后执行,超时不影响)
|
||||
cost_gold = await check_cost(user, plugin, ...)
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **向后兼容**:确保改进不影响现有功能
|
||||
2. **充分测试**:特别是缓存相关的改进
|
||||
3. **监控指标**:记录改进前后的性能指标
|
||||
4. **逐步实施**:先实施低风险改进,再实施高风险改进
|
||||
|
||||
## 预期效果
|
||||
|
||||
- **性能提升**:总体执行时间减少 20-30%
|
||||
- **资源节省**:减少不必要的检查执行 30-50%
|
||||
- **缓存优化**:充分利用 DataAccess 的 Redis 缓存,缓存命中率提升 40-60%
|
||||
- **数据库压力**:减少数据库查询 30-50%(通过缓存)
|
||||
- **用户体验**:响应速度提升,特别是在高并发场景下
|
||||
|
||||
## 关于 DataAccess 缓存的说明
|
||||
|
||||
### DataAccess 已实现的缓存机制
|
||||
|
||||
1. **自动缓存查询结果**
|
||||
|
||||
- 所有通过 `DataAccess.safe_get_or_none()` 等方法的查询结果都会自动缓存
|
||||
- 缓存键基于模型的主键字段(如 `user_id`、`module` 等)
|
||||
|
||||
2. **空结果缓存**
|
||||
|
||||
- 查询结果为 `None` 时,会缓存一个特殊标记(`_NULL_RESULT`)
|
||||
- 默认 TTL 为 5 分钟,避免频繁查询不存在的记录
|
||||
- 可通过 `DataAccess.set_null_result_ttl()` 调整
|
||||
|
||||
3. **缓存统计**
|
||||
|
||||
- 自动统计缓存命中率、命中次数、未命中次数等
|
||||
- 可通过 `DataAccess.get_cache_stats()` 获取统计信息
|
||||
|
||||
4. **缓存失效**
|
||||
- 数据更新、创建、删除时自动清除相关缓存
|
||||
- 支持手动清除:`DataAccess.clear_cache(**kwargs)`
|
||||
|
||||
### 优化建议
|
||||
|
||||
1. **统一使用 DataAccess**:确保所有数据访问都通过 `DataAccess`,而不是直接使用模型类
|
||||
2. **并行获取数据**:利用 `asyncio.gather()` 并行获取多个数据,提高缓存命中率
|
||||
3. **监控缓存效果**:定期查看 `DataAccess.get_cache_stats()`,了解缓存命中率
|
||||
4. **调整空结果 TTL**:根据实际需求调整 `DataAccess.set_null_result_ttl()`,平衡缓存效果和实时性
|
||||
@@ -0,0 +1,234 @@
|
||||
# 权限检查系统优化方案
|
||||
|
||||
## 当前设计分析
|
||||
|
||||
### 优点
|
||||
1. ✅ 模块化设计,每个检查独立
|
||||
2. ✅ 并行执行检查,提高效率
|
||||
3. ✅ 有超时控制和重试机制
|
||||
4. ✅ 有缓存降级策略
|
||||
5. ✅ 有并发控制(信号量)
|
||||
|
||||
### 存在的问题
|
||||
|
||||
#### 1. **缺乏优先级机制**
|
||||
- 所有检查并行执行,没有优先级区分
|
||||
- ban检查应该优先执行,如果用户被ban,其他检查就不需要执行
|
||||
- 浪费计算资源
|
||||
|
||||
#### 2. **缺乏早期退出机制**
|
||||
- 即使某个检查已经失败,其他检查仍然会继续执行
|
||||
- 例如:用户被ban了,但金币检查、限制检查等仍然会执行
|
||||
|
||||
#### 3. **数据获取分散**
|
||||
- 每个检查函数可能都需要查询数据库
|
||||
- 缺乏统一的数据上下文管理
|
||||
- 可能导致重复查询
|
||||
|
||||
#### 4. **错误处理不一致**
|
||||
- 有些检查超时时抛出异常
|
||||
- 有些检查超时时只是记录日志继续执行
|
||||
- 缺乏统一的错误处理策略
|
||||
|
||||
#### 5. **缺乏检查结果缓存**
|
||||
- 对于相同的请求(相同的user_id, group_id, plugin),可能会重复执行相同的检查
|
||||
- ban检查、bot检查等结果可以缓存一段时间
|
||||
|
||||
## 优化方案
|
||||
|
||||
### 方案1:优先级分层检查(推荐)
|
||||
|
||||
#### 核心思想
|
||||
将检查分为多个优先级阶段,高优先级检查失败时立即退出,不执行后续检查。
|
||||
|
||||
#### 检查优先级划分
|
||||
|
||||
```
|
||||
阶段1(CRITICAL - 关键检查):
|
||||
- auth_ban: 用户/群组ban检查
|
||||
- auth_bot: Bot状态检查
|
||||
→ 如果失败,立即退出
|
||||
|
||||
阶段2(HIGH - 高优先级):
|
||||
- auth_plugin: 插件状态检查
|
||||
- auth_group: 群组状态检查
|
||||
→ 并行执行,任一失败则退出
|
||||
|
||||
阶段3(MEDIUM - 中等优先级):
|
||||
- auth_admin: 管理员权限检查
|
||||
- auth_limit: 限制检查
|
||||
→ 并行执行,任一失败则退出
|
||||
|
||||
阶段4(LOW - 低优先级):
|
||||
- auth_cost: 金币检查
|
||||
→ 超时不影响其他检查
|
||||
```
|
||||
|
||||
#### 实现要点
|
||||
|
||||
1. **统一数据上下文**
|
||||
```python
|
||||
@dataclass
|
||||
class AuthContext:
|
||||
"""统一管理所有需要的数据"""
|
||||
plugin: PluginInfo
|
||||
user: UserConsole
|
||||
group: GroupConsole | None
|
||||
bot_id: str
|
||||
entity: EntityIDs
|
||||
# ... 其他数据
|
||||
```
|
||||
|
||||
2. **早期退出机制**
|
||||
```python
|
||||
# 阶段1检查
|
||||
for check in critical_checks:
|
||||
result = await execute_check(check)
|
||||
if not result.success:
|
||||
raise result.error # 立即退出,不执行后续检查
|
||||
```
|
||||
|
||||
3. **检查结果缓存**
|
||||
```python
|
||||
# 对ban、bot等检查结果进行缓存
|
||||
cache_key = f"ban:{user_id}:{group_id}:{module}"
|
||||
if cached_result := get_cached_result(cache_key):
|
||||
return cached_result
|
||||
```
|
||||
|
||||
### 方案2:责任链模式
|
||||
|
||||
#### 核心思想
|
||||
使用责任链模式,按优先级顺序执行检查,任一检查失败则中断链。
|
||||
|
||||
#### 优点
|
||||
- 清晰的检查顺序
|
||||
- 易于添加新的检查
|
||||
- 支持动态调整检查顺序
|
||||
|
||||
#### 缺点
|
||||
- 无法充分利用并行执行的优势
|
||||
- 可能增加总体执行时间
|
||||
|
||||
### 方案3:混合模式(最佳实践)
|
||||
|
||||
#### 核心思想
|
||||
结合优先级和并行执行:
|
||||
- 关键检查(ban、bot)串行执行,失败立即退出
|
||||
- 同优先级检查并行执行
|
||||
- 不同优先级按顺序执行
|
||||
|
||||
## 具体优化建议
|
||||
|
||||
### 1. 立即实施的优化(低风险)
|
||||
|
||||
#### a) 调整检查顺序
|
||||
```python
|
||||
# 在 auth_checker.py 中调整检查顺序
|
||||
# 1. 先执行ban检查(如果失败,立即退出)
|
||||
# 2. 再执行其他检查
|
||||
```
|
||||
|
||||
#### b) 统一数据获取
|
||||
```python
|
||||
# 在auth函数开始处,统一获取所有需要的数据
|
||||
plugin, user, group = await gather_all_data(...)
|
||||
# 然后传递给各个检查函数
|
||||
```
|
||||
|
||||
#### c) 添加检查结果缓存
|
||||
```python
|
||||
# 对ban检查结果缓存30秒
|
||||
@lru_cache(maxsize=1000, ttl=30)
|
||||
async def check_ban_cached(user_id, group_id):
|
||||
return await is_ban(user_id, group_id)
|
||||
```
|
||||
|
||||
### 2. 中期优化(需要重构)
|
||||
|
||||
#### a) 实现优先级机制
|
||||
- 将检查函数注册到优先级系统
|
||||
- 按优先级顺序执行
|
||||
- 高优先级失败时立即退出
|
||||
|
||||
#### b) 统一错误处理
|
||||
- 定义统一的检查结果类型
|
||||
- 所有检查返回统一格式的结果
|
||||
- 统一的超时处理策略
|
||||
|
||||
### 3. 长期优化(架构级改进)
|
||||
|
||||
#### a) 检查结果持久化
|
||||
- 将检查结果存储到Redis
|
||||
- 支持分布式缓存
|
||||
- 减少数据库查询
|
||||
|
||||
#### b) 异步检查队列
|
||||
- 对于非关键检查,可以异步执行
|
||||
- 使用消息队列处理检查任务
|
||||
- 提高响应速度
|
||||
|
||||
## 性能提升预期
|
||||
|
||||
### 当前性能
|
||||
- 所有检查并行执行:~200-500ms
|
||||
- 高并发时可能超时
|
||||
|
||||
### 优化后预期
|
||||
- 关键检查优先:~50-100ms(如果失败)
|
||||
- 完整检查:~150-300ms
|
||||
- 缓存命中:~10-20ms
|
||||
|
||||
### 资源节省
|
||||
- 减少不必要的检查执行:~30-50%
|
||||
- 减少数据库查询:~20-30%
|
||||
- 提高并发处理能力:~40-60%
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 阶段1:准备(1-2天)
|
||||
1. 分析当前检查函数的依赖关系
|
||||
2. 确定优先级划分
|
||||
3. 设计统一的数据上下文
|
||||
|
||||
### 阶段2:实现(3-5天)
|
||||
1. 实现优先级机制
|
||||
2. 实现早期退出机制
|
||||
3. 实现检查结果缓存
|
||||
4. 统一错误处理
|
||||
|
||||
### 阶段3:测试(2-3天)
|
||||
1. 单元测试
|
||||
2. 集成测试
|
||||
3. 性能测试
|
||||
4. 压力测试
|
||||
|
||||
### 阶段4:部署(1天)
|
||||
1. 灰度发布
|
||||
2. 监控性能指标
|
||||
3. 逐步全量
|
||||
|
||||
## 风险评估
|
||||
|
||||
### 低风险
|
||||
- 调整检查顺序
|
||||
- 添加缓存
|
||||
- 统一数据获取
|
||||
|
||||
### 中风险
|
||||
- 实现优先级机制(需要充分测试)
|
||||
- 早期退出机制(可能影响某些边界情况)
|
||||
|
||||
### 高风险
|
||||
- 架构级重构(需要完整的测试覆盖)
|
||||
|
||||
## 建议
|
||||
|
||||
1. **先实施低风险优化**:立即调整检查顺序,添加缓存
|
||||
2. **逐步实施中风险优化**:实现优先级机制,充分测试
|
||||
3. **长期规划高风险优化**:根据实际需求决定是否进行架构级重构
|
||||
|
||||
## 参考实现
|
||||
|
||||
详见 `auth_checker_optimized.py`(设计示例,仅供参考)
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
"""
|
||||
优化后的权限检查系统设计
|
||||
|
||||
主要改进:
|
||||
1. 优先级机制:将检查分为多个优先级阶段,高优先级检查失败时立即退出
|
||||
2. 早期退出:避免不必要的检查执行,提高性能
|
||||
3. 统一数据上下文:在开始前统一获取所有需要的数据
|
||||
4. 检查结果缓存:对相同请求缓存检查结果
|
||||
5. 统一的错误处理:所有检查使用统一的超时和错误处理机制
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import UniMsg
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
|
||||
from zhenxun.models.group_console import GroupConsole
|
||||
from zhenxun.models.plugin_info import PluginInfo
|
||||
from zhenxun.models.user_console import UserConsole
|
||||
from zhenxun.services.data_access import DataAccess
|
||||
from zhenxun.services.log import logger
|
||||
from zhenxun.utils.enum import GoldHandle, PluginType
|
||||
from zhenxun.utils.platform import PlatformUtils
|
||||
from zhenxun.utils.utils import get_entity_ids
|
||||
|
||||
from .auth.auth_admin import auth_admin
|
||||
from .auth.auth_ban import auth_ban
|
||||
from .auth.auth_bot import auth_bot
|
||||
from .auth.auth_cost import auth_cost
|
||||
from .auth.auth_group import auth_group
|
||||
from .auth.auth_limit import LimitManager, auth_limit
|
||||
from .auth.auth_plugin import auth_plugin
|
||||
from .auth.bot_filter import bot_filter
|
||||
from .auth.config import LOGGER_COMMAND, WARNING_THRESHOLD
|
||||
from .auth.exception import (
|
||||
IsSuperuserException,
|
||||
PermissionExemption,
|
||||
SkipPluginException,
|
||||
)
|
||||
|
||||
# 超时设置(秒)—— DataAccess 内部已对单次 DB / 缓存访问做了自己的超时控制;
|
||||
# 这里主要用于控制单个权限检查步骤的上限时间。
|
||||
TIMEOUT_SECONDS = 5.0
|
||||
|
||||
|
||||
# 检查优先级
|
||||
class CheckPriority(IntEnum):
|
||||
"""检查优先级,数值越小优先级越高"""
|
||||
|
||||
CRITICAL = 1 # 关键检查:ban、bot状态
|
||||
HIGH = 2 # 高优先级:插件状态、群组状态
|
||||
MEDIUM = 3 # 中等优先级:管理员权限、限制
|
||||
LOW = 4 # 低优先级:金币检查
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthContext:
|
||||
"""权限检查上下文,统一管理所有需要的数据"""
|
||||
|
||||
plugin: PluginInfo
|
||||
user: UserConsole
|
||||
session: Uninfo
|
||||
matcher: Matcher
|
||||
bot: Bot
|
||||
message: UniMsg
|
||||
group: GroupConsole | None = None
|
||||
bot_id: str = ""
|
||||
entity: Any = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckResult:
|
||||
"""检查结果"""
|
||||
|
||||
success: bool
|
||||
error: Exception | None = None
|
||||
execution_time: float = 0.0
|
||||
cached: bool = False
|
||||
|
||||
|
||||
class AuthChecker:
|
||||
"""优化的权限检查器"""
|
||||
|
||||
async def _execute_check(
|
||||
self,
|
||||
check_func: Callable,
|
||||
check_name: str,
|
||||
priority: CheckPriority,
|
||||
context: AuthContext,
|
||||
**kwargs,
|
||||
) -> CheckResult:
|
||||
"""执行单个检查"""
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
# 执行检查函数
|
||||
await asyncio.wait_for(check_func(**kwargs), timeout=TIMEOUT_SECONDS)
|
||||
result = CheckResult(success=True, execution_time=time.time() - start_time)
|
||||
except SkipPluginException as e:
|
||||
result = CheckResult(
|
||||
success=False, error=e, execution_time=time.time() - start_time
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"{check_name} 检查超时", LOGGER_COMMAND, session=context.session
|
||||
)
|
||||
# 超时时根据优先级决定是否继续
|
||||
if priority <= CheckPriority.HIGH:
|
||||
result = CheckResult(
|
||||
success=False,
|
||||
error=PermissionExemption(f"{check_name} 检查超时"),
|
||||
execution_time=time.time() - start_time,
|
||||
)
|
||||
else:
|
||||
# 低优先级检查超时,允许继续
|
||||
result = CheckResult(
|
||||
success=True, execution_time=time.time() - start_time
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{check_name} 检查失败: {e}", LOGGER_COMMAND, session=context.session
|
||||
)
|
||||
result = CheckResult(
|
||||
success=False, error=e, execution_time=time.time() - start_time
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def _load_context(
|
||||
self, matcher: Matcher, bot: Bot, session: Uninfo, message: UniMsg
|
||||
) -> AuthContext:
|
||||
"""加载权限检查上下文数据"""
|
||||
entity = get_entity_ids(session)
|
||||
module = matcher.plugin_name or ""
|
||||
|
||||
if not module:
|
||||
raise PermissionExemption("Matcher插件名称不存在...")
|
||||
|
||||
# 并行获取所有需要的数据。
|
||||
# DataAccess 内部已经有 Redis 缓存和 DB 超时控制,这里只做一次整体超时保护,
|
||||
# 不再额外手动走 CacheRoot 之类的二级 fallback,避免重复访问 Redis。
|
||||
user_dao = DataAccess(UserConsole)
|
||||
plugin_dao = DataAccess(PluginInfo)
|
||||
group_dao = DataAccess(GroupConsole) if entity.group_id else None
|
||||
|
||||
tasks = {
|
||||
"plugin": plugin_dao.safe_get_or_none(module=module),
|
||||
"user": user_dao.get_by_func_or_none(
|
||||
UserConsole.get_user, False, user_id=entity.user_id
|
||||
),
|
||||
}
|
||||
|
||||
if entity.group_id and group_dao:
|
||||
tasks["group"] = group_dao.safe_get_or_none(
|
||||
group_id=entity.group_id, channel_id__isnull=True
|
||||
)
|
||||
|
||||
try:
|
||||
results = await asyncio.wait_for(
|
||||
asyncio.gather(*tasks.values()), timeout=TIMEOUT_SECONDS
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
# DataAccess 本身已经利用了 Redis / DB 缓存,这里整体超时直接视为失败,
|
||||
# 避免在 Redis 也不稳定时再叠加一层「从缓存再试一次」的复杂 fallback。
|
||||
logger.error(
|
||||
f"加载权限检查所需数据超时,模块: {module}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
raise PermissionExemption("获取权限检查所需数据超时,请稍后再试...")
|
||||
else:
|
||||
plugin = results[0]
|
||||
user = results[1]
|
||||
group = results[2] if len(results) > 2 else None
|
||||
|
||||
if not plugin:
|
||||
raise PermissionExemption(f"插件:{module} 数据不存在...")
|
||||
if plugin.plugin_type == PluginType.HIDDEN:
|
||||
raise PermissionExemption(
|
||||
f"插件: {plugin.name}:{plugin.module} 为HIDDEN..."
|
||||
)
|
||||
if not user:
|
||||
raise PermissionExemption("用户数据不存在...")
|
||||
|
||||
return AuthContext(
|
||||
plugin=plugin,
|
||||
user=user,
|
||||
group=group,
|
||||
bot_id=bot.self_id,
|
||||
session=session,
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
message=message,
|
||||
entity=entity,
|
||||
)
|
||||
|
||||
async def check(
|
||||
self, matcher: Matcher, event: Event, bot: Bot, session: Uninfo, message: UniMsg
|
||||
):
|
||||
"""执行权限检查(优化版本)"""
|
||||
start_time = time.time()
|
||||
cost_gold = 0
|
||||
ignore_flag = False
|
||||
hook_times = {}
|
||||
|
||||
try:
|
||||
# 1. 加载上下文数据
|
||||
context = await self._load_context(matcher, bot, session, message)
|
||||
|
||||
# 2. 按优先级执行检查
|
||||
# 阶段1:关键检查(ban、bot状态)
|
||||
critical_checks = [
|
||||
(
|
||||
"auth_ban",
|
||||
CheckPriority.CRITICAL,
|
||||
lambda: auth_ban(
|
||||
context.matcher, context.bot, context.session, context.plugin
|
||||
),
|
||||
),
|
||||
(
|
||||
"auth_bot",
|
||||
CheckPriority.CRITICAL,
|
||||
lambda: auth_bot(context.plugin, context.bot_id),
|
||||
),
|
||||
]
|
||||
|
||||
for check_name, priority, check_func in critical_checks:
|
||||
result = await self._execute_check(
|
||||
check_func, check_name, priority, context
|
||||
)
|
||||
hook_times[check_name] = f"{result.execution_time:.3f}s"
|
||||
if not result.success:
|
||||
if isinstance(result.error, SkipPluginException):
|
||||
ignore_flag = True
|
||||
raise result.error
|
||||
raise result.error or PermissionExemption(f"{check_name} 检查失败")
|
||||
|
||||
# 3. 执行bot_filter
|
||||
bot_filter(session)
|
||||
|
||||
# 4. 阶段2:高优先级检查(插件状态、群组状态)
|
||||
high_priority_checks = [
|
||||
(
|
||||
"auth_plugin",
|
||||
CheckPriority.HIGH,
|
||||
lambda: auth_plugin(
|
||||
context.plugin, context.group, context.session, event
|
||||
),
|
||||
),
|
||||
(
|
||||
"auth_group",
|
||||
CheckPriority.HIGH,
|
||||
lambda: auth_group(
|
||||
context.plugin,
|
||||
context.group,
|
||||
context.message,
|
||||
context.entity.group_id,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
# 并行执行高优先级检查
|
||||
high_tasks = []
|
||||
for check_name, priority, check_func in high_priority_checks:
|
||||
task = self._execute_check(check_func, check_name, priority, context)
|
||||
high_tasks.append((check_name, task))
|
||||
|
||||
high_results = await asyncio.gather(*[task for _, task in high_tasks])
|
||||
|
||||
for (check_name, _), result in zip(high_tasks, high_results):
|
||||
hook_times[check_name] = f"{result.execution_time:.3f}s"
|
||||
if not result.success:
|
||||
if isinstance(result.error, SkipPluginException):
|
||||
ignore_flag = True
|
||||
raise result.error
|
||||
raise result.error or PermissionExemption(f"{check_name} 检查失败")
|
||||
|
||||
# 5. 阶段3:中等优先级检查(管理员权限、限制)
|
||||
medium_checks = [
|
||||
(
|
||||
"auth_admin",
|
||||
CheckPriority.MEDIUM,
|
||||
lambda: auth_admin(context.plugin, context.session),
|
||||
),
|
||||
(
|
||||
"auth_limit",
|
||||
CheckPriority.MEDIUM,
|
||||
lambda: auth_limit(context.plugin, context.session),
|
||||
),
|
||||
]
|
||||
|
||||
# 并行执行中等优先级检查
|
||||
medium_tasks = []
|
||||
for check_name, priority, check_func in medium_checks:
|
||||
task = self._execute_check(check_func, check_name, priority, context)
|
||||
medium_tasks.append((check_name, task))
|
||||
|
||||
medium_results = await asyncio.gather(*[task for _, task in medium_tasks])
|
||||
|
||||
for (check_name, _), result in zip(medium_tasks, medium_results):
|
||||
hook_times[check_name] = f"{result.execution_time:.3f}s"
|
||||
if not result.success:
|
||||
if isinstance(result.error, SkipPluginException):
|
||||
ignore_flag = True
|
||||
raise result.error
|
||||
raise result.error or PermissionExemption(f"{check_name} 检查失败")
|
||||
|
||||
# 6. 阶段4:低优先级检查(金币检查)
|
||||
try:
|
||||
cost_gold = await asyncio.wait_for(
|
||||
auth_cost(context.user, context.plugin, context.session),
|
||||
timeout=TIMEOUT_SECONDS,
|
||||
)
|
||||
if context.session.user.id in bot.config.superusers:
|
||||
if context.plugin.plugin_type == PluginType.SUPERUSER:
|
||||
raise IsSuperuserException()
|
||||
if not context.plugin.limit_superuser:
|
||||
raise IsSuperuserException()
|
||||
hook_times["cost_gold"] = f"{time.time() - start_time:.3f}s"
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"获取插件费用超时,模块: {context.plugin.module}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
|
||||
except SkipPluginException as e:
|
||||
LimitManager.unblock(
|
||||
matcher.plugin_name or "",
|
||||
get_entity_ids(session).user_id,
|
||||
get_entity_ids(session).group_id,
|
||||
get_entity_ids(session).channel_id,
|
||||
)
|
||||
logger.info(str(e), LOGGER_COMMAND, session=session)
|
||||
ignore_flag = True
|
||||
except IsSuperuserException:
|
||||
logger.debug("超级用户跳过权限检测...", LOGGER_COMMAND, session=session)
|
||||
except PermissionExemption as e:
|
||||
logger.info(str(e), LOGGER_COMMAND, session=session)
|
||||
|
||||
# 扣除金币
|
||||
if not ignore_flag and cost_gold > 0:
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
UserConsole.reduce_gold(
|
||||
get_entity_ids(session).user_id,
|
||||
cost_gold,
|
||||
GoldHandle.PLUGIN,
|
||||
matcher.plugin_name or "",
|
||||
PlatformUtils.get_platform(session),
|
||||
),
|
||||
timeout=TIMEOUT_SECONDS,
|
||||
)
|
||||
hook_times["reduce_gold"] = f"{time.time() - start_time:.3f}s"
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(
|
||||
f"扣除金币超时,模块: {matcher.plugin_name}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
|
||||
# 记录总执行时间
|
||||
total_time = time.time() - start_time
|
||||
if total_time > WARNING_THRESHOLD:
|
||||
logger.warning(
|
||||
f"权限检查耗时过长: {total_time:.3f}s, "
|
||||
f"模块: {matcher.plugin_name}, 详情: {hook_times}",
|
||||
LOGGER_COMMAND,
|
||||
session=session,
|
||||
)
|
||||
|
||||
if ignore_flag:
|
||||
raise IgnoredException("权限检测 ignore")
|
||||
Reference in New Issue
Block a user