创建获取权限的基础方法
This commit is contained in:
62
konabot/common/permsys/__init__.py
Normal file
62
konabot/common/permsys/__init__.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from typing import Iterable
|
||||||
|
import nonebot
|
||||||
|
from nonebot.adapters import Event
|
||||||
|
|
||||||
|
from konabot.common.database import DatabaseManager
|
||||||
|
from konabot.common.path import DATA_PATH
|
||||||
|
from konabot.common.permsys.entity import PermEntity, get_entity_chain
|
||||||
|
from konabot.common.permsys.migrates import execute_migration
|
||||||
|
from konabot.common.permsys.repo import PermRepo
|
||||||
|
|
||||||
|
|
||||||
|
driver = nonebot.get_driver()
|
||||||
|
|
||||||
|
db = DatabaseManager(DATA_PATH / "perm.sqlite3")
|
||||||
|
|
||||||
|
|
||||||
|
class PermManager:
|
||||||
|
def __init__(self, db: DatabaseManager) -> None:
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
async def check_has_permission(
|
||||||
|
self, entities: Event | PermEntity | list[PermEntity], key: str
|
||||||
|
) -> bool:
|
||||||
|
if isinstance(entities, Event):
|
||||||
|
entities = await get_entity_chain(entities)
|
||||||
|
if isinstance(entities, PermEntity):
|
||||||
|
entities = [entities]
|
||||||
|
|
||||||
|
key_split = key.split(".")
|
||||||
|
keys = [".".join(key_split[: i + 1]) for i in range(len(key_split))][::-1]
|
||||||
|
|
||||||
|
async with self.db.get_conn() as conn:
|
||||||
|
repo = PermRepo(conn)
|
||||||
|
# for entity in entities:
|
||||||
|
# for k in keys:
|
||||||
|
# perm = await repo.get_perm_info(entity, k)
|
||||||
|
# if perm is not None:
|
||||||
|
# return perm
|
||||||
|
data = await repo.get_perm_info_batch(entities, keys)
|
||||||
|
for entity in entities:
|
||||||
|
for k in keys:
|
||||||
|
p = data.get((entity, k))
|
||||||
|
if p is not None:
|
||||||
|
return p
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def perm_manager(_db: DatabaseManager | None = None) -> PermManager:
|
||||||
|
if _db is None:
|
||||||
|
_db = db
|
||||||
|
return PermManager(_db)
|
||||||
|
|
||||||
|
|
||||||
|
@driver.on_startup
|
||||||
|
async def _():
|
||||||
|
async with db.get_conn() as conn:
|
||||||
|
await execute_migration(conn)
|
||||||
|
|
||||||
|
|
||||||
|
@driver.on_shutdown
|
||||||
|
async def _():
|
||||||
|
await db.close_all_connections()
|
||||||
61
konabot/common/permsys/entity.py
Normal file
61
konabot/common/permsys/entity.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from nonebot.internal.adapter import Event
|
||||||
|
|
||||||
|
from nonebot.adapters.onebot.v11 import Event as OB11Event
|
||||||
|
from nonebot.adapters.onebot.v11.event import GroupMessageEvent as OB11GroupEvent
|
||||||
|
from nonebot.adapters.onebot.v11.event import PrivateMessageEvent as OB11PrivateEvent
|
||||||
|
|
||||||
|
from nonebot.adapters.discord.event import Event as DiscordEvent
|
||||||
|
from nonebot.adapters.discord.event import GuildMessageCreateEvent as DiscordGMEvent
|
||||||
|
from nonebot.adapters.discord.event import DirectMessageCreateEvent as DiscordDMEvent
|
||||||
|
|
||||||
|
from nonebot.adapters.minecraft.event import MessageEvent as MinecraftMessageEvent
|
||||||
|
|
||||||
|
from nonebot.adapters.console.event import MessageEvent as ConsoleEvent
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PermEntity:
|
||||||
|
platform: str
|
||||||
|
entity_type: str
|
||||||
|
external_id: str
|
||||||
|
|
||||||
|
|
||||||
|
async def get_entity_chain(event: Event) -> list[PermEntity]:
|
||||||
|
entities = [PermEntity("sys", "global", "global")]
|
||||||
|
|
||||||
|
if isinstance(event, OB11Event):
|
||||||
|
entities.append(PermEntity("ob11", "global", "global"))
|
||||||
|
|
||||||
|
if isinstance(event, OB11GroupEvent):
|
||||||
|
entities.append(PermEntity("ob11", "group", str(event.group_id)))
|
||||||
|
entities.append(PermEntity("ob11", "user", str(event.user_id)))
|
||||||
|
|
||||||
|
if isinstance(event, OB11PrivateEvent):
|
||||||
|
entities.append(PermEntity("ob11", "user", str(event.user_id)))
|
||||||
|
|
||||||
|
if isinstance(event, DiscordEvent):
|
||||||
|
entities.append(PermEntity("discord", "global", "global"))
|
||||||
|
|
||||||
|
if isinstance(event, DiscordGMEvent):
|
||||||
|
entities.append(PermEntity("discord", "guilt", str(event.guild_id)))
|
||||||
|
entities.append(PermEntity("discord", "channel", str(event.channel_id)))
|
||||||
|
entities.append(PermEntity("discord", "user", str(event.user_id)))
|
||||||
|
|
||||||
|
if isinstance(event, DiscordDMEvent):
|
||||||
|
entities.append(PermEntity("discord", "channel", str(event.channel_id)))
|
||||||
|
entities.append(PermEntity("discord", "user", str(event.user_id)))
|
||||||
|
|
||||||
|
if isinstance(event, MinecraftMessageEvent):
|
||||||
|
entities.append(PermEntity("minecraft", "global", "global"))
|
||||||
|
entities.append(PermEntity("minecraft", "server", event.server_name))
|
||||||
|
player_uuid = event.player.uuid
|
||||||
|
if player_uuid is not None:
|
||||||
|
entities.append(PermEntity("minecraft", "player", player_uuid.hex))
|
||||||
|
|
||||||
|
if isinstance(event, ConsoleEvent):
|
||||||
|
entities.append(PermEntity("console", "global", "global"))
|
||||||
|
entities.append(PermEntity("console", "channel", event.channel.id))
|
||||||
|
entities.append(PermEntity("console", "user", event.user.id))
|
||||||
|
|
||||||
|
return entities[::-1]
|
||||||
@ -7,11 +7,16 @@ CREATE TABLE perm_entity(
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_perm_entity_lookup
|
||||||
|
ON perm_entity(platform, entity_type, external_id);
|
||||||
|
|
||||||
CREATE TABLE perm_info(
|
CREATE TABLE perm_info(
|
||||||
entity_id INTEGER NOT NULL,
|
entity_id INTEGER NOT NULL,
|
||||||
config_key TEXT NOT NULL,
|
config_key TEXT NOT NULL,
|
||||||
value BOOLEAN NOT NULL,
|
value BOOLEAN,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
-- 联合主键
|
||||||
|
PRIMARY KEY (entity_id, config_key)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TRIGGER perm_entity_update AFTER UPDATE
|
CREATE TRIGGER perm_entity_update AFTER UPDATE
|
||||||
|
|||||||
180
konabot/common/permsys/repo.py
Normal file
180
konabot/common/permsys/repo.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import aiosqlite
|
||||||
|
|
||||||
|
from .entity import PermEntity
|
||||||
|
|
||||||
|
|
||||||
|
def s(p: str):
|
||||||
|
"""读取 SQL 文件内容。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
p: SQL 文件名(相对于当前文件所在目录的 sql/ 子目录)。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SQL 文件的内容字符串。
|
||||||
|
"""
|
||||||
|
return (Path(__file__).parent / "./sql/" / p).read_text()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PermRepo:
|
||||||
|
"""权限实体存储库,负责与数据库交互管理权限实体。
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
conn: aiosqlite 数据库连接对象。
|
||||||
|
"""
|
||||||
|
|
||||||
|
conn: aiosqlite.Connection
|
||||||
|
|
||||||
|
async def create_entity(self, entity: PermEntity) -> int:
|
||||||
|
"""创建新的权限实体并返回其 ID。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: 要创建的权限实体对象。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
新创建实体的数据库 ID。
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: 如果创建后无法获取实体 ID。
|
||||||
|
"""
|
||||||
|
await self.conn.execute(
|
||||||
|
s("create_entity.sql"),
|
||||||
|
(entity.platform, entity.entity_type, entity.external_id),
|
||||||
|
)
|
||||||
|
await self.conn.commit()
|
||||||
|
eid = await self._get_entity_id_or_none(entity)
|
||||||
|
assert eid is not None
|
||||||
|
return eid
|
||||||
|
|
||||||
|
async def _get_entity_id_or_none(self, entity: PermEntity) -> int | None:
|
||||||
|
"""查询实体 ID,如果不存在则返回 None。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: 要查询的权限实体对象。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
实体 ID,如果不存在则返回 None。
|
||||||
|
"""
|
||||||
|
res = await self.conn.execute(
|
||||||
|
s("get_entity_id.sql"),
|
||||||
|
(entity.platform, entity.entity_type, entity.external_id),
|
||||||
|
)
|
||||||
|
row = await res.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
async def get_entity_id(self, entity: PermEntity) -> int:
|
||||||
|
"""获取实体 ID,如果不存在则自动创建。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: 权限实体对象。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
实体的数据库 ID。
|
||||||
|
"""
|
||||||
|
eid = await self._get_entity_id_or_none(entity)
|
||||||
|
if eid is None:
|
||||||
|
return await self.create_entity(entity)
|
||||||
|
return eid
|
||||||
|
|
||||||
|
async def get_perm_info(self, entity: PermEntity, config_key: str) -> bool | None:
|
||||||
|
"""获取实体的权限配置信息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: 权限实体对象。
|
||||||
|
config_key: 配置项的键名。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
配置值(True/False),如果不存在则返回 None。
|
||||||
|
"""
|
||||||
|
eid = await self.get_entity_id(entity)
|
||||||
|
res = await self.conn.execute(
|
||||||
|
s("get_perm_info.sql"),
|
||||||
|
(eid, config_key),
|
||||||
|
)
|
||||||
|
row = await res.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
return row[0]
|
||||||
|
|
||||||
|
async def update_perm_info(
|
||||||
|
self, entity: PermEntity, config_key: str, value: bool | None
|
||||||
|
):
|
||||||
|
"""更新实体的权限配置信息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity: 权限实体对象。
|
||||||
|
config_key: 配置项的键名。
|
||||||
|
value: 要设置的配置值(True/False/None)。
|
||||||
|
"""
|
||||||
|
eid = await self.get_entity_id(entity)
|
||||||
|
await self.conn.execute(s("update_perm_info.sql"), (eid, config_key, value))
|
||||||
|
await self.conn.commit()
|
||||||
|
|
||||||
|
async def get_entity_id_batch(
|
||||||
|
self, entities: list[PermEntity]
|
||||||
|
) -> dict[PermEntity, int]:
|
||||||
|
"""批量获取 Entity 的 eneity_id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entities: PermEntity 列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
字典,键为 PermEntity,值为对应的 ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
for entity in entities:
|
||||||
|
await self.conn.execute(
|
||||||
|
s("create_entity.sql"),
|
||||||
|
(entity.platform, entity.entity_type, entity.external_id),
|
||||||
|
)
|
||||||
|
await self.conn.commit()
|
||||||
|
val_placeholders = ", ".join(["(?, ?, ?)"] * len(entities))
|
||||||
|
params = []
|
||||||
|
for e in entities:
|
||||||
|
params.extend([e.platform, e.entity_type, e.external_id])
|
||||||
|
cursor = await self.conn.execute(
|
||||||
|
f"""
|
||||||
|
SELECT id, platform, entity_type, external_id
|
||||||
|
FROM perm_entity
|
||||||
|
WHERE (platform, entity_type, external_id) IN (VALUES {val_placeholders});
|
||||||
|
""",
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
return {PermEntity(row[1], row[2], row[3]): row[0] for row in rows}
|
||||||
|
|
||||||
|
async def get_perm_info_batch(
|
||||||
|
self, entities: list[PermEntity], config_keys: list[str]
|
||||||
|
) -> dict[tuple[PermEntity, str], bool]:
|
||||||
|
"""批量获取权限信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entities: PermEntity 列表
|
||||||
|
config_keys: 查询的键列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
字典,键是 PermEntity 和 config_key 的元组,值是布尔,过滤掉所有空值
|
||||||
|
"""
|
||||||
|
entity_ids = {
|
||||||
|
v: k for k, v in (await self.get_entity_id_batch(entities)).items()
|
||||||
|
}
|
||||||
|
placeholders1 = ", ".join("?" * len(entity_ids))
|
||||||
|
placeholders2 = ", ".join("?" * len(config_keys))
|
||||||
|
sql = f"""
|
||||||
|
SELECT entity_id, config_key, value
|
||||||
|
FROM perm_info
|
||||||
|
WHERE entity_id IN ({placeholders1})
|
||||||
|
AND config_key IN ({placeholders2})
|
||||||
|
AND value IS NOT NULL;
|
||||||
|
"""
|
||||||
|
|
||||||
|
params = tuple(entity_ids.keys()) + tuple(config_keys)
|
||||||
|
cursor = await self.conn.execute(sql, params)
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
|
||||||
|
return {(entity_ids[row[0]], row[1]): row[2] for row in rows}
|
||||||
11
konabot/common/permsys/sql/create_entity.sql
Normal file
11
konabot/common/permsys/sql/create_entity.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
INSERT
|
||||||
|
OR IGNORE INTO perm_entity(
|
||||||
|
platform,
|
||||||
|
entity_type,
|
||||||
|
external_id
|
||||||
|
)
|
||||||
|
VALUES(
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?
|
||||||
|
);
|
||||||
8
konabot/common/permsys/sql/get_entity_id.sql
Normal file
8
konabot/common/permsys/sql/get_entity_id.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
SELECT
|
||||||
|
id
|
||||||
|
FROM
|
||||||
|
perm_entity
|
||||||
|
WHERE
|
||||||
|
perm_entity.platform = ?
|
||||||
|
AND perm_entity.entity_type = ?
|
||||||
|
AND perm_entity.external_id = ?;
|
||||||
7
konabot/common/permsys/sql/get_perm_info.sql
Normal file
7
konabot/common/permsys/sql/get_perm_info.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
SELECT
|
||||||
|
VALUE
|
||||||
|
FROM
|
||||||
|
perm_info
|
||||||
|
WHERE
|
||||||
|
entity_id = ?
|
||||||
|
AND config_key = ?;
|
||||||
4
konabot/common/permsys/sql/update_perm_info.sql
Normal file
4
konabot/common/permsys/sql/update_perm_info.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
INSERT INTO perm_info (entity_id, config_key, value)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT(entity_id, config_key)
|
||||||
|
DO UPDATE SET value=excluded.value;
|
||||||
Reference in New Issue
Block a user