♻️ refactor(command): 配置系统从 SQLite 迁移至 YAML 并修复白名单失效

- 用 CommandConfig dataclass 单例替代模块级变量,解决 from import 造成的本地绑定不随 global 更新的 bug
- 删除 db.py,改用 settings.yaml 存储动态配置,首次启动自动创建并合并 .env 默认值
- 新增文件轮询 watcher(2 秒),检测 YAML 变更自动热重载
- 管理界面 API 改为直接读写 YAML,即时生效
- 依赖 aiosqlite 替换为 pyyaml
This commit is contained in:
2026-05-03 18:23:29 +08:00
parent 9ffe78a9c2
commit f82363f45f
8 changed files with 314 additions and 270 deletions

View File

@@ -2,22 +2,7 @@
from aiohttp import web
from ..config import (
COMMAND_AT_SENDER,
COMMAND_CALLBACK_TIMEOUT,
COMMAND_CALLBACK_URL,
COMMAND_DENIED_GROUPS,
COMMAND_DENIED_USERS,
COMMAND_ALLOWED_GROUPS,
COMMAND_ALLOWED_USERS,
COMMAND_LENGTH_MAX,
COMMAND_LENGTH_MIN,
COMMAND_LIST_MODE,
COMMAND_PREFIX,
COMMAND_SCOPE,
reload_settings,
)
from ..db import get_settings, update_settings
from ..config import command, get_settings_flat, reload_settings, update_settings_from_api
from ..response import error, ok
@@ -25,8 +10,7 @@ from ..response import error, ok
async def api_get_settings(request: web.Request) -> web.Response:
"""GET /api/settings — 返回全部动态配置。"""
settings = await get_settings()
return ok(data=settings)
return ok(data=get_settings_flat())
async def api_update_settings(request: web.Request) -> web.Response:
@@ -39,37 +23,17 @@ async def api_update_settings(request: web.Request) -> web.Response:
if not isinstance(data, dict):
return error("request body must be a json object")
# 允许更新的 key 白名单
allowed_keys = {
"command_prefix",
"command_length_min",
"command_length_max",
"command_scope",
"command_list_mode",
"command_allowed_groups",
"command_denied_groups",
"command_allowed_users",
"command_denied_users",
"command_at_sender",
"command_callback_url",
"command_callback_timeout",
}
filtered = {k: str(v) for k, v in data.items() if k in allowed_keys}
filtered = update_settings_from_api(data)
if not filtered:
return error("no valid settings to update")
await update_settings(filtered)
reload_settings(filtered)
return ok(data=filtered, msg="配置已更新并生效")
async def api_reload_settings(request: web.Request) -> web.Response:
"""POST /api/settings/reload — 从数据库重新加载配置。"""
settings = await get_settings()
reload_settings(settings)
return ok(msg="配置已从数据库重新加载")
"""POST /api/settings/reload — 从 settings.yaml 重新加载配置。"""
reload_settings()
return ok(msg="配置已从 settings.yaml 重新加载")
# ── 管理页面 ─────────────────────────────────────────────────
@@ -568,7 +532,7 @@ body {
<div class="header-right">
<span class="header-badge" id="statusBadge">● 运行中</span>
<button class="btn btn-sm btn-primary" onclick="saveAll()">保存配置</button>
<button class="btn btn-sm btn-default" onclick="reloadFromDb()">重新加载</button>
<button class="btn btn-sm btn-default" onclick="reloadFromYaml()">重新加载</button>
</div>
</header>
@@ -750,7 +714,7 @@ body {
</div>
<div style="display:flex; justify-content:space-between; padding:8px 0; border-bottom:1px solid var(--border-color); font-size:13px;">
<span style="color:var(--text-secondary);">存储</span>
<span>SQLite</span>
<span>YAML</span>
</div>
</div>
</div>
@@ -933,13 +897,13 @@ async function saveAll() {
}
}
// ── 从数据库重新加载 ──────────────────────────────────────
async function reloadFromDb() {
// ── 从 YAML 重新加载 ──────────────────────────────────────
async function reloadFromYaml() {
try {
const resp = await fetch('/api/settings/reload', { method: 'POST' });
const json = await resp.json();
if (json.code === 0) {
showToast('已从数据库重新加载', 'success');
showToast('已从 YAML 重新加载', 'success');
await loadSettings();
} else {
showToast('重新加载失败: ' + json.msg, 'error');