Files
ClassHelper/scripts/nsis/server-installer.nsi
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

177 lines
7.8 KiB
NSIS
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.
; ClassHelper 服务端 + Web 管理端 Windows 安装程序
; 由 scripts/dist-server.mjs 生成(占位符在打包时替换)
;
; 设计要点:
; - 当前用户级安装($LOCALAPPDATA\Programs),无需管理员权限即可完成安装
; - 内置 Node 运行时,目标机无需安装 Node.js
; - 快捷方式:启动/停止/重启服务、打开管理端、卸载
; - 默认加入当前用户开机自启(注册表 HKCU\...\Run),随时可用
; - 卸载时保留 data 目录(数据库)以避免误删数据
Unicode true
SetCompressor /SOLID lzma
!include "MUI2.nsh"
!include "FileFunc.nsh"
!define PRODUCT "@PRODUCT@"
!define VERSION "@VERSION@"
!define STAGING "@STAGING@"
!define OUT_FILE "@OUTFILE@"
!define ICON_FILE "@ICON@"
!define APP_PORT "@PORT@"
Name "${PRODUCT} ${VERSION}"
OutFile "${OUT_FILE}"
InstallDir "$LOCALAPPDATA\Programs\ClassHelperServer"
InstallDirRegKey HKCU "Software\ClassHelperServer" "InstallDir"
RequestExecutionLevel user
ShowInstDetails show
ShowUninstDetails show
!define MUI_ICON "${ICON_FILE}"
!define MUI_UNICON "${ICON_FILE}"
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_TITLE "安装完成"
!define MUI_FINISHPAGE_TEXT "ClassHelper 服务端已安装完成。$\r$\n$\r$\n若勾选了「开机自启动」,服务会在登录 Windows 后自动运行(可随时在管理端「系统设置」里关闭)。浏览器打开 http://127.0.0.1:${APP_PORT}/ 即可使用管理端。$\r$\n$\r$\n默认账号:admin / admin123(首次登录后请立即修改密码)"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "立即启动服务并打开管理端"
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchService
!define MUI_LICENSEPAGE_TEXT_TOP "请阅读以下说明后继续安装。"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${STAGING}\LICENSE.txt"
!insertmacro MUI_PAGE_DIRECTORY
; 组件页:让"开机自启动"成为**可选**项(需求:「支持选择开机自启动」)。
; 默认勾选(保持既有行为),取消勾选则完全不写注册表 Run 项 ——
; 装完之后仍然可以在 Web 端「系统设置」里随时开关(PUT /api/system/autostart)。
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "SimpChinese"
Function LaunchService
ExecShell "open" "$INSTDIR\start.cmd" "" SW_SHOWMINIMIZED
Sleep 3000
ExecShell "open" "http://127.0.0.1:${APP_PORT}/"
FunctionEnd
; ------------------------------------------------------------------ 安装
Section "主程序" SecMain
SectionIn RO
SetOutPath "$INSTDIR"
; 若目标目录已有旧版本,先停止正在运行的服务,避免文件占用
IfFileExists "$INSTDIR\data\server.pid" 0 +3
nsExec::ExecToLog '"$INSTDIR\stop.cmd"'
Sleep 1500
; 升级安装时保留原有配置(含 JWT 密钥与端口设置),避免用户被强制重新登录
IfFileExists "$INSTDIR\.env" 0 +2
Rename "$INSTDIR\.env" "$INSTDIR\.env.previous"
; 先清掉旧版本的代码目录再铺新文件:
; 否则 web/assets 里的旧 hash 分片、server/dist 旧模块、node_modules 旧依赖会残留,
; 出现"新旧版本混装"(例如 shared/dist 是旧的,服务启动直接报找不到导出)。
RMDir /r "$INSTDIR\web"
RMDir /r "$INSTDIR\server"
RMDir /r "$INSTDIR\node_modules"
File /r "${STAGING}\*.*"
IfFileExists "$INSTDIR\.env.previous" 0 +3
Delete "$INSTDIR\.env"
Rename "$INSTDIR\.env.previous" "$INSTDIR\.env"
WriteRegStr HKCU "Software\ClassHelperServer" "InstallDir" "$INSTDIR"
WriteRegStr HKCU "Software\ClassHelperServer" "Version" "${VERSION}"
WriteUninstaller "$INSTDIR\uninstall.exe"
; 开始菜单
CreateDirectory "$SMPROGRAMS\${PRODUCT}"
CreateShortCut "$SMPROGRAMS\${PRODUCT}\启动服务.lnk" "$INSTDIR\start.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\${PRODUCT}\停止服务.lnk" "$INSTDIR\stop.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\${PRODUCT}\重启服务.lnk" "$INSTDIR\restart.cmd" "" "$INSTDIR\node.exe" 0 SW_SHOWMINIMIZED
CreateShortCut "$SMPROGRAMS\${PRODUCT}\打开管理端.lnk" "http://127.0.0.1:${APP_PORT}/"
CreateShortCut "$SMPROGRAMS\${PRODUCT}\使用说明.lnk" "$INSTDIR\README.txt"
CreateShortCut "$SMPROGRAMS\${PRODUCT}\卸载.lnk" "$INSTDIR\uninstall.exe"
; 桌面快捷方式(管理端进浏览器)
CreateShortCut "$DESKTOP\${PRODUCT}.lnk" "http://127.0.0.1:${APP_PORT}/"
; 开机自启改到**组件页里的可选 Section**(见下面的 SecAutostart):
; 这里不再无条件写注册表,由用户在组件页勾选决定(需求:「支持选择开机自启动」)。
; 注册到"应用和功能"列表,便于通过系统设置卸载
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayName" "${PRODUCT} ${VERSION}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayIcon" "$INSTDIR\uninstall.exe"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "DisplayVersion" "${VERSION}"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "Publisher" "Class Helper"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer" "EstimatedSize" "$0"
; 首次启动即初始化数据库并开始提供服务
ExecShell "open" "$INSTDIR\start.cmd" "" SW_SHOWMINIMIZED
SectionEnd
; ------------------------------------------------------------------ 开机自启动
;
; 独立成一个 Section,于是它会出现在组件页上(默认勾选 = 与历史行为一致),
; 用户可以取消勾选、装完之后也能在 Web 端「系统设置」里随时开关
; (服务端接口 `PUT /api/system/autostart`,Windows 走的就是这个注册表 Run 项)。
Section "开机自启动(登录 Windows 后自动启动服务,推荐)" SecAutostart
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "ClassHelperServer" '"$INSTDIR\start.cmd"'
SectionEnd
; ------------------------------------------------------------------ 卸载
Section "Uninstall"
; 停止服务
IfFileExists "$INSTDIR\data\server.pid" 0 +3
nsExec::ExecToLog '"$INSTDIR\stop.cmd"'
Sleep 1200
; 兜底:按命令行特征结束残留进程
nsExec::ExecToLog 'taskkill /F /FI "WINDOWTITLE eq ClassHelperServer*"'
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "ClassHelperServer"
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\ClassHelperServer"
DeleteRegKey HKCU "Software\ClassHelperServer"
Delete "$DESKTOP\${PRODUCT}.lnk"
RMDir /r "$SMPROGRAMS\${PRODUCT}"
; 保留数据目录,避免误删数据库:重命名为 data.backup
IfFileExists "$INSTDIR\data\classhelper.db" 0 +3
RMDir /r "$INSTDIR\data.backup"
Rename "$INSTDIR\data" "$INSTDIR\data.backup"
RMDir /r "$INSTDIR\server"
RMDir /r "$INSTDIR\web"
RMDir /r "$INSTDIR\node_modules"
RMDir /r "$INSTDIR\logs"
Delete "$INSTDIR\node.exe"
Delete "$INSTDIR\package.json"
Delete "$INSTDIR\package-lock.json"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\start.cmd"
Delete "$INSTDIR\stop.cmd"
Delete "$INSTDIR\restart.cmd"
Delete "$INSTDIR\status.cmd"
Delete "$INSTDIR\install-service.cmd"
Delete "$INSTDIR\uninstall-service.cmd"
Delete "$INSTDIR\README.txt"
Delete "$INSTDIR\uninstall.exe"
; 若用户未保留 .env,则一并清理目录
Delete "$INSTDIR\.env"
RMDir "$INSTDIR"
SectionEnd