refactor: schema 收敛到 ORM metadata——5 张裸表升模型,方言无关的建表/补列/唯一索引
原来建表有三套来源(ORM create_all + 自建 SCHEMA_MIGRATIONS + agent_api 里的裸
CREATE TABLE)。这在 SQLite 下能活是因为 SQLite 的 DDL 极宽容;换 MySQL 后
agent_* 四张表的 `INTEGER PRIMARY KEY AUTOINCREMENT`、`TEXT DEFAULT ''`、
`TEXT PRIMARY KEY` 会全部建不出来,而失败被 `except: pass` 吞掉 —— 表现为
「经验库/动作库/会话功能静默失灵、日志里什么都看不到」。本期把它收敛成一套。
- core/models.py:
* 新增 5 个 ORM 模型:AppMeta / AgentExperience / ExperienceAudit /
AgentAction / AgentConversation(列名沿用历史,`app_meta.key` 在 MySQL 里是
保留字,ORM 属性名用 k,读写统一走 db_config.meta_get/meta_set)
* 新增 _long_text()(MySQL 用 MEDIUMTEXT,裸 TEXT 只有 64KB)与 _DOUBLE()
(评分别用单精度 FLOAT);_migrate_schema 不再执行 DDL,只维护版本账本,
SCHEMA_MIGRATIONS 的历史建表/加列条目 SQL 置 None
* 新增 _sync_columns():用 sqlalchemy.inspect 比对模型与实表补缺列,取代原来
靠 "duplicate column name" 报错文本判断的写法(换方言就失效)
* _ensure_unique_indexes() 按方言分叉:MySQL 5.7 没有过滤索引,改用
「虚拟生成列 + 唯一索引」复刻"空值不参与唯一约束"的语义
- web/agent_api.py: 删 4 段裸 CREATE TABLE;_ensure_* 收敛为 _ensure_tables()
(db.create_all 薄封装,失败必记日志);app_meta 读写与 INSERT OR IGNORE
改为方言中立
- core/device_discovery.py: app_meta 读写改用同一助手
- core/system_backup.py: SUMMARY_TABLES 由 db.metadata 派生 —— 备份覆盖红线
从"靠人记"变成结构上不可能漏;CURRENT_SCHEMA_VERSION 改从 models 导入
验证(均在 users.db 的一致快照副本上,不碰真实库):
空库 → 自建 12 张表 + 默认管理员 + 唯一索引 + schema_version=6;
老库 → 逐表行数与真实库完全一致,app_meta 键未变;
接口 → health/devices/jobs/groups/agent(经验库/动作库/会话) 全 200,
会话增删与备份导出(12 张表、无覆盖缺失)正常。
This commit is contained in:
+6
-13
@@ -26,26 +26,19 @@ from urllib.parse import quote
|
||||
from config import (DATA_DIR, APK_DIR, BACKUP_DIR,
|
||||
RESTORE_STAGING_DIR, RESTORE_PENDING_DIR)
|
||||
from core.logger import get_logger
|
||||
from core.models import SCHEMA_MIGRATIONS
|
||||
from core.models import CURRENT_SCHEMA_VERSION, db
|
||||
|
||||
_log = get_logger("core.backup")
|
||||
|
||||
# 当前代码期望的 schema 版本(来自版本化迁移表)
|
||||
CURRENT_SCHEMA_VERSION = max(v[0] for v in SCHEMA_MIGRATIONS) if SCHEMA_MIGRATIONS else 0
|
||||
|
||||
DB_FILE = os.path.join(DATA_DIR, "users.db")
|
||||
|
||||
# 判定「本平台备份库」的必需表(缺任何一张即拒绝导入)
|
||||
REQUIRED_TABLES = ("app_meta", "user", "task_job", "device_group")
|
||||
# 预览/清单里展示行数的业务表(存在才统计)
|
||||
# ⚠️ 红线:**新增任何持久化表,必须同步登记到这里**——备份覆盖清单以此为准,
|
||||
# 漏登记的表在导出清单/预览里不可见(2026-09-10 动作库 agent_action 即因此
|
||||
# 被误判为"没有备份":数据其实在 users.db 快照里,只是清单没列)。
|
||||
SUMMARY_TABLES = (
|
||||
"app_meta", "user", "device_group", "task_job", "custom_action",
|
||||
"apk_file", "device", "pending_device",
|
||||
"agent_conversation", "agent_experience", "experience_audit", "agent_action",
|
||||
)
|
||||
# 备份覆盖清单:**由模型元数据派生**,不再手工维护。
|
||||
# 这样「新增持久化表必须登记进备份清单」这条红线从"靠人记"变成结构上不可能漏
|
||||
# (2026-09-10 动作库 agent_action 就漏过:数据其实在快照里,只是清单没列,
|
||||
# 导出预览里看不到那一行 → 被误判成"没备份")。
|
||||
SUMMARY_TABLES = tuple(sorted(t.name for t in db.metadata.tables.values()))
|
||||
TABLE_LABELS = {
|
||||
"app_meta": "系统配置(app_meta)",
|
||||
"user": "用户", "device_group": "设备分组", "task_job": "任务计划",
|
||||
|
||||
Reference in New Issue
Block a user