Files
ClassHelper/docs/management/database.md
T
LoveChengke 817af02357 feat(permissions,clients,ai,system,server,web,client,shared)!: 权限组 + 客户端设备远控与自动更新 + 大模型助手 + 系统诊断(1.3.0)
功能级准入改为「权限键」,`requireRole` 退成默认模板。

权限组
- 权限目录(39 键:键 / 中文名 / 分类 / 默认角色)收口到 shared,路由挂 requirePermission / requireAnyPermission
- 有效权限每请求实时算,改完分组立刻生效:ADMIN 全集且 permission:manage、system:manage 不可剥夺;
  CLASS_DEVICE 空集(不受分组影响);有分组 = 并集(因此也能用来**收回**权限);无分组 = 角色默认模板
- 新增「权限组」页:组增删改、成员分配、生效权限预览
- 班级维度的科目规则(assertCanManageSubjectContent)故意不可被权限组覆盖

客户端设备(ClassHelper 桌面端)
- 设备身份是 deviceKey(首次启动生成的 UUID,saveConfig 拒绝改写、退出登录也保留)
- 心跳走「DB 记状态 + 客户端定时拉」,不引入新的长连接:30 秒一次,在线窗口 90 秒
- 远控命令 shutdown / restart / logoff / lock:服务端有效期 10 分钟,客户端弹默认选中「取消」的对话框、
  60 秒未确认按取消处理,执行后必须回执(重复回执幂等),同一台机器同一条命令只弹一次
- 新增「客户端设备」页:版本一览、一键推送更新、下发远控

客户端安装包由服务端下载并缓存(教室机常常没有外网,浏览器也受 CSP 限制)
- POST /api/update/client/fetch 由服务端下载到 data/client-releases/<版本>/ 并登记 sha256 + size
- 自动安装三条护栏:没有 sha256 就拒收、必须等到非上课且主窗口隐藏满 5 分钟、冒烟模式永不安装

大模型助手:只解析、不落库
- POST /api/ai/interpret 返回**待确认**的结构化动作(建作业 / 发通知 / 叫人),
  真正落库仍调既有端点 —— 权限断言、上课时段紧急通知保护、ClassIsland 推送全部自动复用
- 模型给的 params 用与手动入口**同一份** zod 再校验(模型输出不可信)
- API Key 走 AES-256-GCM(密钥由 JWT_SECRET 经 scrypt 派生)加密存 AppSetting,只进不出
- provider=mock 是离线确定性解析,自动化验收一律用它;每次解析写一条 AiAudit(不含密钥)

系统与运维
- 开发者模式(服务端内存缓存开关,热路径不查库;开启后 500 才附 error.stack)、运行诊断(绝不回传密钥)
- 开机自启动运行期可改:Windows 改 HKCU\...\Run、Linux systemctl enable/disable,
  **非安装形态一律返回 supported:false**,不往开发机的注册表里写自启项
- 服务端侧栏折叠只用 CSS 收拢(不重建 DOM),关怀模式只放大字号与控件、不动任何几何令牌

客户端
- 崩溃自愈:渲染进程挂掉 / 超 10 秒无响应先静默重建主窗口(10 分钟最多 3 次),
  未捕获异常只在「没在上课、没人看着」时才重启进程;冒烟模式绝不重启(会杀掉验证实例)
- 上线补弹离线期间收到的消息只做一次:起点只在「可达 → 不可达」的边界写,恢复时取最近 5 条按时间升序推

发布内容批量
- POST /notifications/bulk、POST /homeworks/bulk:**单班失败不影响整批**,逐班返回结果
- 作业的科目名跨班**按名匹配**已有课程,找不到就整班记失败,绝不自动建课
  (自动建课会把创建者写成任课老师,等于越权改任教关系)
- 通知与作业同口径按「所属日期」归档,新增 /notifications/days 与 /notifications/creators

BREAKING CHANGE: 能不能做某件事不再看角色名,而是看权限键(GET /api/permissions/me)。
未分组的账号按角色默认模板生效,行为与 1.2.0 一致;分组之后生效集合只由组的并集决定。
服务端首次启动会自动应用 1 个数据库迁移(权限组 3 表 / AppSetting / 客户端设备·命令·安装包 /
AiAudit / Notification.assignDate / User.careMode,历史通知按本地日期回填),升级前请先备份。
2026-10-11 11:49:32 +08:00

79 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 数据库管理
description: 备份与恢复、导出导入、定时备份,以及 Web 管理端里的「数据库管理」模块。
---
# 数据库管理
Web 管理端有一个「数据库管理」页(`/database`,**仅管理员可见**,后端同样 `requireRole('ADMIN')`),
把过去要手工做的数据库运维都收进了界面。
## 它能做什么
| 能力 | 说明 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 连接状态检测 | 当前类型(SQLite / MySQL)、连通性与延迟、引擎版本、数据体积、23 张表的行数、连接串(MySQL 密码脱敏) |
| 连接测试 | 对任意目标库试连(切换前的第一步) |
| **一键切换** | SQLite ⇄ MySQL:自动「备份当前库 → 改写 schema provider → 生成客户端 / 建表 → 子进程迁移全部数据 → 改写 `.env`」,异步任务带步骤进度,**失败自动回滚** |
| 备份 / 恢复 | gzip JSON 快照存在 `<数据目录>/backups/`;手动备份 / 从备份恢复(整库覆盖)/ 删除 |
| 定时备份 | enabled + 间隔小时数 + 保留份数,配置存在 `data/database-settings.json`;服务端每 10 分钟检查一次到点任务(**进程不运行不补跑**) |
| 快捷导入导出 | 下载快照 JSON(跨库通用)/ 下载数据库文件(仅 SQLite)/ 导入快照(整库覆盖,base64 上传) |
## 关键设计:只有一种快照格式
**备份、导入导出、跨库迁移共用同一种「JSON 快照」**:
- 23 张表按**拓扑序**导出;
- 恢复时临时**关闭外键检查** —— 因为 `User.classId → Class` 与 `Class.teacherId → User`
是**互相引用**的,任何排序都会被其中一侧卡死;
- 分批 `createMany` 写入。
因此**任意一份备份都能恢复回任意一种受支持的数据库**(SQLite 备份可以恢复进 MySQL,反之亦然)。
MySQL 的连接测试 / 建表 / 写入依赖随包内置的 `@prisma/adapter-mariadb`,不需要额外安装。
## 三条边界
1. **主库只支持 SQLite 与 MySQL**。Redis 这类内存键值库 Prisma 不支持建表 / 迁移,
接口层直接 422 拒绝;PostgreSQL 暂未纳入。
2. **切换完成后必须重启服务端**(安装版跑 `restart.cmd`,开发模式重跑 `pnpm dev:server`)才会连上新库。
切换失败会自动回滚 `schema.prisma`,`.env` 只在**数据全部迁移成功后**才改写。
3. **目标库必须为空**(有表就拒绝)。
> 安装包体积因此从约 34MB 涨到约 62MB:切换需要随包带上 prisma CLI 与 TypeScript 编译器
> (Prisma 7 的生成器产出 `.ts` 源码,切换后要在目标机上编译进 `dist`)。
## 命令行 / SSH 场景的备份
Web 界面适合日常;服务器上更适合用命令。Linux 安装形态有一套现成的:
```bash
sudo classhelper backup # 备份配置 + 数据库
sudo classhelper backup --note "升级前" --keep 7
sudo classhelper backup list
sudo classhelper backup restore 20261005-033000 # 用某个备份覆盖当前配置与数据(会先停服务)
sudo classhelper backup schedule daily # 每天 03:30 自动备份(systemd timer,保留 7 份)
sudo classhelper backup schedule off
```
备份落在 `/var/backups/classhelper/<时间戳>[-说明]/`:`config.env` + `classhelper.db` + `meta.json`,
目录权限 700、文件 600。
- SQLite 用 `VACUUM INTO` 做**一致性快照** —— 服务在跑也安全,比直接 `cp` 可靠得多;
- MySQL 用 `mysqldump --single-transaction`,密码经环境变量传给子进程,**不进 argv**;
- `restore` 前会先自动备份现状;恢复的对象是**配置与数据**,不是程序。
## 其它形态怎么备份
| 形态 | 备份 | 恢复 |
| ----------------- | ------------------------------------------------------ | ---------------------------- |
| Windows 安装包 | 停服务后复制 `data\` 目录(里面有 `classhelper.db`) | 替换后启动 |
| Linux | `classhelper backup`(见上) | `classhelper backup restore` |
| Docker | 备份挂载出来的数据卷 | 还原数据卷 |
| MySQL(任意形态) | `mysqldump -u classhelper -p classhelper > backup.sql` | `mysql … < backup.sql` |
**建议**:每日自动备份 + 保留 7 份,并且**定期真的恢复一次**验证备份可用。
> 变更 `JWT_SECRET` 会让所有用户重新登录,无必要时不要换。
> Linux 上换用 `sudo classhelper key rotate`。